読者です 読者をやめる 読者になる 読者になる

ゆとりーなの日記

日記的な事を書いて行くと思はれる

まぁやりはじめたんですけどね。

事の発端は水曜日、合氣道部で晩飯を食べていた時。後輩が「隅須さんパンドラバッテリー持ってますか?」と突然聞いてきたんですね。そういうの持ってそうに見えるんですかね。「PSP改造とか東方移植すんぜ!って言ってたとき以来だな」とか呟いたら、「俺、紅魔やったことないから移植してよ」って言い出す同期約一名。期末が近づくと無性にこういうことがやりたくなるもの、早速移植作業開始ですよ。
まずは東方紅魔郷を引っ張り出してきて、dat解凍機にかけて画像やら音声を吸い出して、PSPSDKインストール、サンプルとにらめっこして最初の画面表示、キー入力、音楽再生まではひとまずいけました。携帯ゲーム機は一応組み込みっぽい要素?があるのでPCの時と同じノリで作るとすぐにメモリが吹っ飛ぶので苦戦気味です。vectorとかで大きな領域確保するともうぴちゅんします。何故か同じサイズでも静的領域だとセーフだったりするので、そこらへんを留意しつつ、最初の画面を表示するコードは次のようになりました。

// main.cc
#include <boost/noncopyable.hpp>
#include "application.h"
#include "loading.h"

class framework : private boost::noncopyable {
 public:
  framework() : sequence_(new loading()) {
  }
  void operator ()() {
    std::shared_ptr<sequence> temp = sequence_->update();
    if (temp) {
      sequence_ = temp;
    }
  }
 private:
  std::shared_ptr<sequence> sequence_; // psp-gcc4.3にunique_ptrなかった
};

int main() {
  application app;
  framework fw;
  return app.run(std::ref(fw));
}

// attribute.h
#ifndef TH06_PSP_ATTRIBUTE_H_
#define TH06_PSP_ATTRIBUTE_H_

// VC++2010が__attribute__に波線を打たないようにする
#ifndef __GNUC__ 
#define __attribute__(x) 
#endif 

#endif

// application.h
#ifndef TH06_PSP_APPLICATION_H_
#define TH06_PSP_APPLICATION_H_
#include <functional>
#include <boost/noncopyable.hpp>
#include <pspkernel.h>
#include <pspgu.h>
#include "attribute.h"

class application : private boost::noncopyable {
 public:
  application();
  ~application();
  static int run(std::function<void ()> func);
 private:
  void init();
  static int setup_callbacks();
  static int callback_thread(SceSize, void *);
  static int exit_callback(int, int, void *);
  static unsigned int __attribute__((aligned(16))) display_list_[262144];
};

#endif

// application.cc
#include "application.h"
#include <fstream>
#include <pspdisplay.h>
#include <pspaudiolib.h>
#include <pspaudio.h>
#include <pspctrl.h>
PSP_MODULE_INFO("th06_psp", 0, 1, 1);
PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | THREAD_ATTR_VFPU);

struct vertex{
  float u, v;
  float x, y, z;
};

application::application() {
  init();
}

application::~application() {
}

int application::run(std::function<void ()> func) {
  for (;;) {
    // 描画準備
    sceGuStart(GU_DIRECT, display_list_);
    sceGuTexMode(GU_PSM_8888, 0, 0, GU_FALSE);
    sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGBA);
    sceGuTexFilter(GU_NEAREST, GU_NEAREST);
    func();
    sceGuFinish();
    sceGuSync(0, GU_SYNC_FINISH);
    sceGuSwapBuffers();
  }
  sceGuTerm();
  sceKernelExitGame();
  return 0;
}

void application::init() {
  setup_callbacks();
  // PSPの3D描画エンジン?の初期化
  sceGuInit();
  sceGuStart(GU_DIRECT, display_list_);
  sceGuDrawBuffer(GU_PSM_8888, NULL, 512);
  sceGuDispBuffer(480, 272, reinterpret_cast<void *>(512 * 272 * 4), 512);
  sceGuOffset(0, 0);
  sceGuViewport(240, 136, 480, 272);
  sceGuScissor(0, 0, 480, 272);
  sceGuEnable(GU_SCISSOR_TEST);
  sceGuEnable(GU_TEXTURE_2D);
  sceGuFinish();
  sceGuSync(0, GU_SYNC_FINISH);
  sceDisplayWaitVblankStart();
  sceGuDisplay(GU_TRUE);
}

// 以下三つは決まりごとくさい
int application::setup_callbacks() {
  const int thread_id = sceKernelCreateThread("update_thread", &callback_thread, 0x11, 0xFA0, 0, 0);
  if (thread_id > 0) {
    sceKernelStartThread(thread_id, 0, 0);
  }
  return thread_id;
}

int application::callback_thread(SceSize, void *) {
  const int cb_id = sceKernelCreateCallback("Exit Callback", &exit_callback, NULL);
  sceKernelRegisterExitCallback(cb_id);
  sceKernelSleepThreadCB();
  return 0;
}

int application::exit_callback(int, int, void *) {
  sceKernelExitGame();
  return 0;
}


unsigned int __attribute__((aligned(16))) application::display_list_[262144];

// vertex.h
#ifndef TH06_PSP_VERTEX_H_
#define TH06_PSP_VERTXX_H_

// 順番はUV、XYZの順らしい
struct image_vertex {
  float u_;
  float v_;
  float x_;
  float y_;
  float z_;
};

#endif

// back_image.h
#ifndef TH06_PSP_BACK_IMAGE_H_
#define TH06_PSP_BACK_IMAGE_H_
#include <string>
#include <boost/noncopyable.hpp>
#include "attribute.h"

class back_image : private boost::noncopyable {
 public:
  static void set_image(const std::string &file_name);
  static void draw();
 private:
  static unsigned int __attribute__((aligned(16)))pixel_[480 * 272];
};

#endif

// back_image.cc
#include "back_image.h"
#include <array>
#include <fstream>
#include <pspgu.h>
#include "vertex.h"

void back_image::set_image(const std::string &file_name) {
  std::ifstream fin(file_name.c_str(), std::ios::in | std::ios::binary);
  fin.seekg(0x80);
  fin.read(reinterpret_cast<char *>(pixel_), 480 * 272 * 16);
}

void back_image::draw() {
  sceGuTexImage(0, 512, 512, 480, pixel_);
  // ちょっとDirectXチックな感じ
  const std::array<image_vertex, 2> v = {{
    {0.f, 0.f, 0.f, 0.f, 0.f},
    {480.f, 272.f, 480.f, 272.f, 0.f}
  }};
  sceGuDrawArray(GU_SPRITES, GU_TEXTURE_32BITF | GU_VERTEX_32BITF | GU_TRANSFORM_2D, 2, 0, &v.front());
}

unsigned int __attribute__((aligned(16)))back_image::pixel_[480 * 272];

// sequence.h
#ifndef TH06_PSP_SEQUENCE_H_
#define TH06_PSP_SEQUENCE_H_
#include <memory>

class sequence {
public:
  virtual ~sequence() {}
  virtual std::shared_ptr<sequence> update() = 0;
};

#endif

// loading.h
#ifndef TH06_PSP_LOADING_H_
#define TH06_PSP_LOADING_H_
#include <boost/noncopyable.hpp>
#include "sequence.h"

class loading : public sequence, private boost::noncopyable {
 public:
  loading();
  virtual std::shared_ptr<sequence> update();
};

#endif

// loading.cc
#include "loading.h"
#include "back_image.h"
#include "application.h"

loading::loading() : count_(0), next_(&self) {
  back_image::set_image("th06logo.img");
}

std::shared_ptr<sequence> loading::update() {
  back_image::draw();
  return std::shared_ptr<sequence>();
}

画像ファイルについては、png読み込むのがめんどうだったので、構造が単純なDDSファイルを使おうとおもったのですが、RGBAの並びのRとBがPSPSDKのそれとひっくり返っていたので、これらをひっくり返す変換機を作る羽目になりました。
で、成果です。

エミュ上で起動できてます。はい。これだけです。
著作権の辺りとか大丈夫かあやしいですが、

の前例とかもあるのでまぁ何とかなりますかね。
追記:

タイトル画面も書けました。画面サイズに合わせて縮小したらロゴがぼけて読みにくくなってしまいました。