在 Windows 10 上编译 RetroArch 核心 和 fbneo PC 独立客户端以及EmulatorJS的详细步骤

第一阶段:基础环境配置 (MSYS2)
无论你想编译哪种版本,都必须先完成这一步。

下载并安装 MSYS2
前往 MSYS2 官网或直接下载安装包:msys2-x86_64-20240507.exe 。

https://htst.iok.la/obj/tos-cn-i-ik7evvg4ik/1431212d55ac44b89b415f3a975fa9d7.exe

安装完成后,从开始菜单启动 MSYS2 MSYS(命令行管理程序)。
更新系统环境
在命令行中输入以下命令更新包数据库:

pacman --noconfirm -Sy

安装基本运行时库:

pacman --needed --noconfirm -S bash pacman pacman-mirrors msys2-runtime

重要:关闭当前的 MSYS2 窗口并重新启动它 。
执行全面系统更新:

pacman --noconfirm -Su

再次关闭窗口,准备进入具体的编译步骤

第二阶段:选择编译目标
🎯 目标 A:编译 RetroArch 核心 (DLL)
如果你是为了在 RetroArch 模拟器中使用 FBNeo,请执行此部分。
1.启动环境
从开始菜单打开 MSYS2 MINGW64 窗口(用于编译 64 位核心)。

2.安装编译依赖工具
复制并执行以下命令安装 64 位工具链(包含 gcc, make, git 等):

pacman -S --noconfirm --needed wget git make mingw-w64-x86_64-toolchain mingw-w64-x86_64-ntldd mingw-w64-x86_64-zlib mingw-w64-x86_64-pkg-config mingw-w64-x86_64-SDL2 mingw-w64-x86_64-libxml2 mingw-w64-x86_64-freetype mingw-w64-x86_64-python3 mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-drmingw

3.获取源码
拉取 FBNeo 项目代码(如果你已经有源码,可跳过此步):

git clone https://github.com/libretro/FBNeo.git

4.进入项目目录
假设源码在你的默认路径,进入目录(根据实际路径调整):

cd FBNeo

注:文档提示如果是 D 盘路径 D:\libretro-fbneo,则输入

cd /d/libretro-fbneo

5.执行编译命令
清理旧文件(可选,推荐):

make -j5 -C ./src/burner/libretro clean
make -j5 -C ./src/burner/libretro generate-files-clean  #此操作会清掉driverlist.h文件,编译Android到时候需要重新生成

生成必要文件:

make -j5 -C ./src/burner/libretro generate-files

开始编译(-j5 代表线程数,可根据你 CPU 核心数调整):

make -j5 -C ./src/burner/libretro

6.获取成品
编译完毕后即可在 ./src/burner/libretro 获得 fbneo_libretro.dll 核心
generate-file 会在项目根目录生成gamelist.txt 游戏列表
压缩核心大小

strip ./src/burner/libretro/fbneo_libretro.dll

编译 PC 街机独立客户端 (fbneo64.exe)
如果你想要一个独立的 .exe 程序直接运行游戏,请执行此部分。

1.启动环境
确保打开的是 MSYS2 MINGW64 窗口 。

2.安装客户端专用依赖
独立客户端需要额外的工具(如 nasm, zip 等),请执行:

pacman -S --needed msys2-devel base-devel binutils mingw-w64-x86_64-toolchain mingw-w64-x86_64-nasm zip wget

3.进入项目目录

cd FBNeo

通过 cd 命令进入 FBNeo 源码根目录(方法同上)。

4.执行编译命令 (64位正式版)
输入以下命令开始构建:

mingw32-make mingw -j 6 BUILD_X64_EXE=1 SKIPDEPEND=1 RELEASEBUILD=1

参数说明:
BUILD_X64_EXE=1: 生成 64 位程序。
RELEASEBUILD=1: 生成正式发布版(如果不加此参数可能是 Debug 版)。

5.获取成品
编译成功后,在项目根目录下会生成 fbneo64.exe 。

6.压缩体积 (可选)
如果觉得生成的 exe 文件太大,可以安装 UPX 进行压缩:
安装 UPX:

pacman -S upx

执行压缩:

upx --best --lzma fbneo64.exe

文件路径在msys64\home\Administrator

在 Windows 10 上使用 MSYS2(推荐使用 UCRT64 或 MINGW64 终端)编译 EmulatorJS/build 的详细步骤:
1. 准备 MSYS2 环境
打开 MSYS2 的终端(推荐 MSYS2 UCRT64 或 MSYS2 MINGW64),首先更新系统并安装必要的依赖包。
执行以下命令安装依赖:

pacman -Syu
pacman -S git mingw-w64-ucrt-x86_64-python3 mingw-w64-ucrt-x86_64-jq mingw-w64-ucrt-x86_64-cmake make zip p7zip wget base-devel mingw-w64-x86_64-jq make

注意:p7zip-full 在 MSYS2 中通常叫 p7zip,python3 需要安装对应环境的版本。

2. 获取代码
如果还没下载代码,先克隆仓库:

git clone https://github.com/EmulatorJS/build.git emulatorjs-build
cd emulatorjs-build

3. 修改并配置 Emscripten (build_env.sh)
你提供的 build_env.sh 脚本在 Windows/MSYS2 下可能会因为路径或符号链接问题报错。我们需要手动执行这部分,或者稍微修改一下逻辑。
建议直接手动执行以下命令来替代运行 build_env.sh:

# 1. 确保在项目根目录
rm -rf emsdk
git clone https://github.com/emscripten-core/emsdk.git

# 2. 安装 Emscripten
cd emsdk
./emsdk install 3.1.74
./emsdk activate 3.1.74
# 3. 设置环境变量(这一步很关键)
source ./emsdk_env.sh
# 4. 回到根目录
cd ..

5. 开始编译
在完成了上述依赖安装和环境变量配置后,你可以开始编译了。
编译所有核心:

cd emulatorjs-build
source ~/emsdk/emsdk_env.sh
make clean
# 运行构建脚本,编译所有核心
bash build.sh

我们最好只编译fbneo,执行下面命令:

bash build.sh --core=fbneo

编译结束后,emulatorjs-build\output目录下的fbneo.zip就是我们编译好的文件。
windows 10上build.sh修复版

#!/bin/bash

# This script is used to build the cores for RetroArch using EmulatorJS
# It will pull the cores.json file from the root of the repository and build the cores

# argument defaults
coreToBuild=""
listAllCores=false
listCoreNames=false

# process arguments
for i in "$@"
do
case $i in
    -c=*|--core=*)
        # core to build
        coreToBuild="${i#*=}"
        shift
        ;;

    -l|--list)
        # list cores
        listAllCores=true
        ;;

    --core-names-only)
        # list core names only
        listCoreNames=true
        ;;

    *)
        # unknown option
        ;;
esac
done

# set up paths
initialPath="$PWD"
buildPath="$PWD/compile"
outputPath="$PWD/output"
buildReport="$outputPath/reports"
logPath="$outputPath/logs"
outPath="$buildPath/RetroArch/emulatorjs"
tempPath="RetroArch/emulatorjs/core-temp"

# ---------------------------------------------------------------------------
# [FIX 1] Define Override Arguments
# Explicitly set compilers to use commands from PATH instead of absolute Windows paths
# AR=emar and LD=em++ are critical for correct linking in MSYS2
# ---------------------------------------------------------------------------
OVERRIDE_ARGS="CC=emcc CXX=em++ AR=emar LD=em++"

build() {
    rm -f *.bc
    # [FIX 2] Use /usr/bin/make to avoid Windows path issues
    emmake /usr/bin/make -f "$makefileName" clean
    emmake /usr/bin/make -j$(nproc --all) -f "$makefileName" platform=emscripten $OVERRIDE_ARGS $makefileArg || exit 1
    linkerfilename=( *.bc )
    mv $linkerfilename "$buildPath/$tempPath/normal/"
}
buildThreads() {
    rm -f *.bc
    emmake /usr/bin/make -f "$makefileName" clean
    emmake /usr/bin/make -j$(nproc --all) -f "$makefileName" platform=emscripten EMULATORJS_THREADS=1 $OVERRIDE_ARGS $makefileArg || exit 1
    linkerfilename=( *.bc )
    mv $linkerfilename "$buildPath/$tempPath/threads/"
}
buildLegacy() {
    rm -f *.bc
    emmake /usr/bin/make -f "$makefileName" clean
    emmake /usr/bin/make -j$(nproc --all) -f "$makefileName" platform=emscripten EMULATORJS_LEGACY=1 $OVERRIDE_ARGS $makefileArg || exit 1
    linkerfilename=( *.bc )
    mv $linkerfilename "$buildPath/$tempPath/legacy/"
}
buildThreadsLegacy() {
    rm -f *.bc
    emmake /usr/bin/make -f "$makefileName" clean
    emmake /usr/bin/make -j$(nproc --all) -f "$makefileName" platform=emscripten EMULATORJS_THREADS=1 EMULATORJS_LEGACY=1 $OVERRIDE_ARGS $makefileArg || exit 1
    linkerfilename=( *.bc )
    mv $linkerfilename "$buildPath/$tempPath/legacyThreads/"
}

# create compile directory
mkdir -p $buildPath
cd $buildPath

# create output path
mkdir -p $outputPath
cp $initialPath/cores.json $outputPath
mkdir -p $buildReport
mkdir -p $logPath

if [ "$listAllCores" = false ]; then
    # start pulling sources and compile
    # Note: Using hub.dd.ci mirror as per your previous logs for better connectivity
    if [ ! -d "RetroArch" ]; then
        git clone --depth 1 -b next "https://hub.dd.ci/EmulatorJS/RetroArch.git" "RetroArch" || exit 1
    fi
    cd RetroArch
    git pull
    if [ ! -d "EmulatorJS" ]; then
        git clone "https://hub.dd.ci/EmulatorJS/EmulatorJS.git" "EmulatorJS" --depth 1 || exit 1
    fi
    cd EmulatorJS
    git pull

    cd "$outPath"
    rm -f *.bc
fi

compileProject() {
    name="$1"
    repo="$2"
    branch="$3"
    makefilePath="$4"
    makefileName="$5"
    makefileArg="$6"
    custom="$7"
    build_command="$8"
    requireThreads="$9"

    if [ ! -d "$name" ]; then
        git clone "$repo" "$name" --depth 1
        cd "$name"
        git submodule update --init --recursive
        cd ../
    fi
    cd "$name"
    if [ $branch != 'null' ]; then
        echo "Checking out branch $branch"
        git checkout "$branch"
    fi
    git pull
    git submodule update --recursive

    if [[ "$custom" = "true" ]]; then
        eval "$build_command"
    else
        cd "$makefilePath"

        # only build the unthreaded version if requireThreads is false
        if [ "$requireThreads" = false ]; then
            build
            buildLegacy
        fi

        # build the threaded version
        buildThreads
        buildThreadsLegacy
    fi

    cd "$buildPath"
}

cd "$buildPath"

compileStartPath="$PWD"
for row in $(jq -r '.[] | @base64' ../cores.json); do
    # function to decode base64 and parse JSON
    # [FIX 4] Added tr -d '\r' to clean Windows carriage returns
    _jq() {
        echo ${row} | tr -d '\r' | base64 --decode | jq -r ${1}
    }

    # get core name
    name=`echo $(_jq '.') | jq -r '.name'`

    # check if we need to build this core, if coreToBuild is not set, build all cores
    if [ ! -z "$coreToBuild" -a "$coreToBuild" != " " ]; then
        if [ "$coreToBuild" != "$name" ]; then
            continue
        fi
    fi

    # get the core details
    repo=`echo $(_jq '.') | jq -r '.repo'`
    branch=`echo $(_jq '.') | jq -r '.branch'`
    license=`echo $(_jq '.') | jq -r '.license'`
    buildpath=`echo $(_jq '.') | jq -r '.makeoptions.buildpath'`
    makescript=`echo $(_jq '.') | jq -r '.makeoptions.makescript'`
    arguments=`echo $(_jq '.') | jq -r '.makeoptions.arguments[] | @base64'`
    options=`echo $(_jq '.') | jq -r '.options'`
    optRequireThreads=`echo $(_jq '.') | jq -r '.options.requireThreads'`
    custom=`echo $(_jq '.') | jq -r '.makeoptions.custom'`
    build_command=`echo $(_jq '.') | jq -r '.makeoptions.build_command'`
    build_retroarch_command=`echo $(_jq '.') | jq -r '.makeoptions.build_retroarch_command'`

    argumentstring=""
    for rowarg in $(echo "${arguments}"); do
        # [FIX 5] Added tr -d '\r' here as well
        argumentstring="$argumentstring `echo $rowarg | tr -d '\r' | base64 --decode`"
    done

    # display core details
    if [ "$listCoreNames" = true ]; then
        if [ "$listAllCores" = true ]; then
            echo $name
        fi

        if [ "$listAllCores" = false ]; then
            $listCoreNames=false
        fi
    fi

    # set requireThreads to false if it's not true
    if [ -z "$optRequireThreads" ] || [ "$optRequireThreads" != "true" ]; then
        requireThreads=false
    else
        requireThreads=true
    fi

    if [ "$listCoreNames" = false ]; then
        echo "Core: $name"
        echo "Repo: $repo"
        echo "Branch: $branch"
        echo "License: $license"
        echo "Build path: $buildpath"
        echo "Make script: $makescript"
        echo "Arguments: $argumentstring"
        echo "Options: $options"
        echo "Require threads: $requireThreads"
        echo "Custom: $custom"
        echo "Build command: $build_command"
        echo "Build RetroArch command: $build_retroarch_command"
        echo "---------------------------------------"
    fi

    # build if listAllCores is set to false
    if [ "$listAllCores" = false ]; then
        echo "Building core $name"
        
        # create temp directory for core
        cd "$buildPath"
        rm -fr $tempPath
        mkdir -p $tempPath/
        cd $tempPath
        mkdir -p normal/
        mkdir -p threads/
        mkdir -p legacy/
        mkdir -p legacyThreads/
        cd "$buildPath"

        # start compile
        startTime=`date -u -Is`

        cd $compileStartPath

        echo "Working dir $PWD"

        unset FROZEN_CACHE
        
        compileProject "$name" "$repo.git" "$branch" "$buildpath" "$makescript" "$argumentstring" "$custom" "$build_command" "$requireThreads" >> "$logPath/$name-compile.log"

        # write JSON stanza for this core to disk
        # [FIX 6] Added tr -d '\r' here too
        echo ${row} | tr -d '\r' | base64 --decode > "./core.json"

        if [ ! -z "$license" -a "$license" != " " ]; then
            # license file is provided - copy it
            echo "License file: $name/$license"
            cp $name/$license "./license.txt"
        fi

        echo "Building wasm's for core $name"
        cd "$buildPath/RetroArch/emulatorjs"

        if [[ "$custom" = "true" ]]; then
            eval "$build_retroarch_command" >> "$logPath/$name-emake.log"
        else
            # [FIX 3 - CRITICAL] 
            # 1. Use 'env' to force CC/CXX variables, preventing internal makefiles from using Windows paths.
            # 2. Use 'bash' explicitly to run the .sh script on Windows.
            if [ "$requireThreads" = false ]; then
                mv core-temp/normal/*.bc ./
                emmake /usr/bin/env CC=emcc CXX=em++ AR=emar LD=em++ bash ./build-emulatorjs.sh --clean >> "$logPath/$name-emake.log"
                rm -f *.bc

                mv core-temp/legacy/*.bc ./
                emmake /usr/bin/env CC=emcc CXX=em++ AR=emar LD=em++ bash ./build-emulatorjs.sh --clean --legacy >> "$logPath/$name-emake.log"
                rm -f *.bc
            fi

            mv core-temp/threads/*.bc ./
            emmake /usr/bin/env CC=emcc CXX=em++ AR=emar LD=em++ bash ./build-emulatorjs.sh --clean --threads >> "$logPath/$name-emake.log"
            rm -f *.bc

            mv core-temp/legacyThreads/*.bc ./
            emmake /usr/bin/env CC=emcc CXX=em++ AR=emar LD=em++ bash ./build-emulatorjs.sh --clean --threads --legacy >> "$logPath/$name-emake.log"
            rm -f *.bc

            rm -rf core-temp
        fi
        rm -f *.bc

        echo "Packing core information for $name"
        cd $compileStartPath

        if [ $requireThreads = false ]; then
            if [ -f "EmulatorJS/data/cores/$name-wasm.data" ]; then
                7z a -t7z EmulatorJS/data/cores/$name-wasm.data ./core.json ./license.txt ../build.json
                cp EmulatorJS/data/cores/$name-wasm.data $outputPath
            fi

            if [ -f "EmulatorJS/data/cores/$name-legacy-wasm.data" ]; then
                7z a -t7z EmulatorJS/data/cores/$name-legacy-wasm.data ./core.json ./license.txt ../build.json
                cp EmulatorJS/data/cores/$name-legacy-wasm.data $outputPath
            fi
        fi

        if [ -f "EmulatorJS/data/cores/$name-thread-wasm.data" ]; then
            7z a -t7z EmulatorJS/data/cores/$name-thread-wasm.data ./core.json ./license.txt ../build.json
            cp EmulatorJS/data/cores/$name-thread-wasm.data $outputPath
        fi

        if [ -f "EmulatorJS/data/cores/$name-thread-legacy-wasm.data" ]; then
            7z a -t7z EmulatorJS/data/cores/$name-thread-legacy-wasm.data ./core.json ./license.txt ../build.json
            cp EmulatorJS/data/cores/$name-thread-legacy-wasm.data $outputPath
        fi

        # create zip file containing the core data files
        cd EmulatorJS/data/cores
        zip $outputPath/$name.zip $name-thread*wasm.data
        if [ $requireThreads = false ]; then
            zip -u $outputPath/$name.zip  $name-wasm.data $name-legacy-wasm.data
        fi

        cd $compileStartPath

        # clean up to make sure the next build in the json gets the right license and core file
        rm -f ./license.txt
        rm -f ./core.json
        
        # write report to report file
        endTime=`date -u -Is`
        reportString="{ \"core\": \"$name\", \"buildStart\": \"$startTime\", \"buildEnd\": \"$endTime\", \"options\": $options }"
        buildReportFile="$buildReport/$name.json"
        echo $reportString > $buildReportFile
    fi
done

if [ "$listAllCores" = false ]; then
    # delete all compile files
    if [[ -z "$DEPLOY_ENV" ]]; then
        echo "Not deleting build path"
    else
        rm -fR $buildPath
    fi
fi

cd "$initialPath"

同样的下面是build-emulatorjs.sh在windows 10上编译的修复版

#!/bin/bash
set +e

. ../version.all

if [[ -z "$EMSCRIPTEN" ]] ; then
  echo "Run this script with emmake. Ex: emmake $0"
  exit 1
fi

for i in "$@"; do
  case $i in
    --threads)
      PTHREADS=YES
      shift
      ;;
    --legacy)
      LEGACY=YES
      shift
      ;;
    --clean)
      CLEAN=YES
      shift
      ;;
    *)
      echo "Unknown option $i"
      echo "Usage: $0 [option] ..."
      echo "Options:"
      echo "  --threads"
      echo "  --legacy"
      echo "  --clean"
      exit 1
      ;;
  esac
done

clean () {
  # [FIX 1] Force use of MSYS2 make
  /usr/bin/make -C ../ -f Makefile.emulatorjs clean || exit 1
}
containsElement () {
  local e match="$1"
  shift
  for e; do
    if [[ "$e" == "$match" ]]; then
      echo 1
      return 0
    fi
  done
  echo 0
  return 1
}

if [[ "$CLEAN" = "YES" ]]; then
  clean
fi

lastGles=0

largeStack=("mupen64plus_next")
largeHeap=("mupen64plus_next" "picodrive" "pcsx_rearmed" "genesis_plus_gx" "genesis_plus_gx_wide" "mednafen_psx" "mednafen_psx_hw" "parallel_n64" "ppsspp")
needsGles3=("ppsspp")
needsThreads=("ppsspp")
largeThreads=("ppsspp")
noCHD=("mame2003" "mame2003_plus" "pcsx_rearmed" "genesis_plus_gx" "genesis_plus_gx_wide")
no7Zip=("bsnes")

for f in $(ls -v *_emscripten.bc); do
  name=`echo "$f" | sed "s/\(_libretro_emscripten\|\).bc$//"`
  async=1
  sevenZip=1
  wasm=1
  gles3=1
  stack_mem=4194304 # 4mb
  heap_mem=134217728 # 128mb
  pthread=0
  chd=1
  threads=0

  if [ "$LEGACY" = "YES" ]; then
    gles3=0
  fi

  if [[ "$PTHREADS" = "YES" ]]; then
    threads=1
    pthread=4
  fi

  if [[ $(containsElement $name "${largeStack[@]}") = 1 ]]; then
    stack_mem=134217728 # 128mb
  fi
  if [[ $(containsElement $name "${largeHeap[@]}") = 1 ]]; then
    heap_mem=536870912 # 512mb
  fi
  if [[ $(containsElement $name "${needsThreads[@]}") = 1 && $pthread = 0 ]]; then
    echo "$name"' requires threads! Please build with --threads! Exiting...'
    exit 1
  fi
  if [[ $(containsElement $name "${noCHD[@]}") = 1 ]]; then
    chd=0
  fi
  if [[ $(containsElement $name "${largeThreads[@]}") = 1 ]]; then
    pthread=32
  fi
  if [[ $(containsElement $name "${no7Zip[@]}") = 1 ]]; then
    sevenZip=0
  fi
  if [[ $(containsElement $name "${needsGles3[@]}") = 1 && $gles3 = 0 ]]; then
    echo "$name"' does not support gles2 (legacy)! Please build without --legacy! Exiting...'
    exit 1
  fi

  echo "-- Building core: $name --"
  cp -f "$f" ../libretro_emscripten.a
    
  echo NAME: $name
  echo ASYNC: $async
  echo HAVE_THREADS: $threads
  echo PTHREAD_POOL_SIZE: $pthread
  echo GLES3: $gles3
  echo STACK_SIZE: $stack_mem
  echo INITIAL_HEAP: $heap_mem
  echo HAVE_CHD: $chd
  echo HAVE_7ZIP: $sevenZip

  if [[ "$CLEAN" = "YES" ]]; then
    if [ $lastGles != $gles3 ] ; then
        clean
    fi
  fi
  lastGles=$gles3

  # Compile core
  # [FIX 2] Use /usr/bin/make and nproc --all
  # [FIX 3] Explicitly pass CC/CXX variables to override environment pollution
  echo "BUILD COMMAND: /usr/bin/make -C ../ -f Makefile.emulatorjs HAVE_7ZIP=$sevenZip HAVE_CHD=$chd HAVE_THREADS=$threads PTHREAD_POOL_SIZE=$pthread ASYNC=$async HAVE_OPENGLES3=$gles3 STACK_SIZE=$stack_mem INITIAL_HEAP=$heap_mem TARGET=${name}_libretro.js -j"$(nproc --all)
  /usr/bin/make -C ../ -f Makefile.emulatorjs HAVE_7ZIP=$sevenZip HAVE_CHD=$chd HAVE_THREADS=$threads PTHREAD_POOL_SIZE=$pthread ASYNC=$async HAVE_OPENGLES3=$gles3 STACK_SIZE=$stack_mem INITIAL_HEAP=$heap_mem TARGET=${name}_libretro.js CC=emcc CXX=em++ AR=emar LD=em++ -j$(nproc --all) || exit 1

  # Move executable files
  out_dir="../../EmulatorJS/data/cores"
  out_name=""

  mkdir -p $out_dir

  core=""
  if [ $name = "mednafen_vb" ]; then
    core="beetle_vb"
  else
    core=${name}
  fi

  out_name=${core}

  if [[ $pthread != 0 ]] ; then
    out_name="${out_name}-thread"
  fi
  if [[ $gles3 = 0 ]] ; then
    out_name="${out_name}-legacy"
  fi
  out_name="${out_name}-wasm.data"

  if [ $wasm = 0 ]; then
    7z a ${out_dir}/${out_name} ../${name}_libretro.js.mem ../${name}_*.js
    rm ../${name}_libretro.js.mem
  else
    7z a ${out_dir}/${out_name} ../${name}_libretro.wasm ../${name}_*.js
    rm ../${name}_libretro.wasm
  fi
  rm -f ../${name}_libretro.js
done

路径是emulatorjs-build\compile\RetroArch\emulatorjs

点赞

发表回复