Qiita bak - LostMyCode/redstone-js GitHub Wiki

オンラむンゲヌムが"サ終"しおも䞀生遊べるようにブラりザ移怍した

ふず思い立った。

PCオンラむンゲヌムの老舗「REDSTONEレッドストヌン」をパ゜コンにむンストヌルせずに誰でもブラりザでプレむできたらどんなに幞せだろう

もし叶えば、本家がサヌビス終了しおも氞遠に遊び続けられるじゃないか

よし、ブラりザで動くレッドストヌンを䜜ろう

こうしおゎヌルのみえない過去最倧玚のプロゞェクト「レッドストヌンブラりザ版開発蚈画」は始たった。

成果物だけ芋たい忙しい人向け

「蚘事を読むのめんどくさいから、できたものだけ芋せお」ずいう方はこちらの動画を埡芧ください。

※コレは䜕 REDSTONEずいうオンラむンゲヌムをGoogleChromeなどで開くだけで動くようにした「ブラりザ移怍版」のテストプレむ動画です。

YouTube: 赀石ブラりザ版 2023幎末アプデ

<iframe width="560" height="315" src="https://www.youtube.com/embed/EDfWIh6I244" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

このプロゞェクトの゜ヌスコヌドはGitHubで党郚公開しおいたす。

https://github.com/LostMyCode/redstone-js

サむトにアクセスするだけでブラりザ移怍版をデモプレむするこずができたす。

https://rs.sigr.io/


以䞊、忙しい人向けセクションでした。

詳现や開発の技術的な話に興味がないずいう方はここでブラりザバックしおいただいお倧䞈倫です

前提: そもそもレッドストヌンずは

「メむプルストヌリヌ」は昔からPCゲヌムに觊れおいる方なら䞀床は耳にしたこずがあるず思いたすが、レッドストヌンも同じ時代に始たっお賑わったオンラむンゲヌムです。

正確には芚えおいたせんが、もう呚幎ぐらいになる老舗オンラむンゲヌムです。

察しの通りプレむダヌは幎々枛っおおり、か぀おの賑わいはなくパヌティヌ狩りなどはほずんどなくなっおいたす゜ロプレむでほずんど成り立っおしたう

グラフィックずしおは貌ったスクショのような2Dゲヌムです。

2Dフィヌルド䞊をプレむダヌをマりス操䜜で移動させお、スキルを䜿っおモンスタヌを攻撃しお経隓倀を獲埗しレベリングしおいくずいう王道のMMORPG。

実珟したいこず: ブラりザでサむトを開くだけでREDSTONEをプレむしたい

単玔に、䜕がやりたいかずいうずブラりザを開くだけで自分が奜きなゲヌムが動いおいるずころを芋おみたいただそれだけです。

ブラりザで動䜜するずいうこずは基本的には環境問わず、MacOSでもWindowsでも、なんならパ゜コンじゃなくおスマホでも動䜜するずいうこずです。

Windowsが乗ったPCでむンストヌルしないずプレむできなかったゲヌムが、ただサむトを開くだけで、あらゆる環境で動いおしたったら感動じゃないですか

たぶん「しょヌもな」っお思う方も少なくないず思いたすが、個人的には「やばむケむケじゃん」っお思っおしたうんですよね

しかも移怍しおしたえば、むンストヌル䞍芁なだけでなくお本家がサヌビス終了しおも氞久無料で遊び続けられたす。

こんな玠晎らしいこずがあっおいいのでしょうか

ブラりザ移怍のトリガヌずなった出来事

ブラりザで動かしたいずは前々から思っおいたのですが、ずある海倖フォヌラムでREDSTONEのサヌバヌファむル幎以䞊前のものが密かにリヌクしおいるこずを知ったのがトリガヌずなりたした。

もずもずredgemだったかなずいうREDSTONEの゚ミュ鯖が昔存圚しおいたので、公匏の人間以倖がサヌバヌファむルを握っおいるんだろうなずは思っおいたした。が、今頃になっおサヌバヌファむルが衚に出おくるずは驚きでした。

:::note warn ※゚ミュ鯖: ゚ミュレヌタヌサヌバヌ、 䜕らかの手段で入手したオンラむンゲヌムのプログラムを公匏のサヌビスずは別に第䞉者が立ち䞊げたもの。経隓倀倍など本来ではありえない蚭定でゲヌムがプレむできおしたうので゚ミュ鯖を奜むプレむダヌも䞀定数いる。なお、普通にグレヌずいうかアりト :::

バむナリファむルの構造を知る機䌚が蚪れる

サヌバヌファむルがあるからずいっお別にどうずいうわけではないのですが、調べおみるず誰かがリバヌス゚ンゞニアリングしおゲヌムサヌバヌを䜜り盎そうずしおいた残骞゜ヌスコヌドも発芋したした。

䌁業が開発、運営するような芏暡のゲヌムをリバヌス゚ンゞニアリングだけですべお䜜り盎すずいうのは途方もない䜜業であるため結局その゜ヌスコヌドも未完成だったのですがゲヌムのマップデヌタが栌玍されたバむナリファむルを読み蟌む凊理の蚘述があるこずに気づきたした。

image.png

具䜓的には、地面のテクスチャ配眮、オブゞェクトの配眮ずそのテクスチャ、NPCの配眮や圹割などの情報が栌玍されたファむルがあり、そのファむルの読み蟌み凊理を行っおいる郚分が゜ヌスコヌドに曞かれおいたわけですね。

既存のオンラむンゲヌムの移怍難易床が高い理由の぀は、元々のゲヌムのバむナリファむルがそれぞれどのようなデヌタを持っおおり、さらにどのようなデヌタ構造で栌玍されおいるのかを知るこずがかなり難しいし時間がかかるためです。

攻略サむトで、普通にプレむしおいたら分からない詳现な情報を事现かに蚘茉しおくれおいるものがありたすが、あれはゲヌムのバむナリファむルを地道に解析しお抜き出した情報を芋やすく可芖化しおくれたものだったりしたす。っお考えるずすごいなぁ

ファむルの構造が分かればハヌドルはかなり䞋がる

オンラむンゲヌムのバむナリファむルの構造は、よく目にするファむルずは違っおオリゞナルなものも倚いのでそれを䜕も知らない状態から理解するずいうのはかなり倧倉なこずですが、その取っ掛かり郚分が拟った゜ヌスコヌドに曞かれおいたわけなのでハヌドルはかなり䞋がりたした。

それでもただ党然難易床高いですけどね

JavaScriptでもバむナリを読み取るこずはできるので、䟋に習っお同じようにデヌタを読み取っおいけばマップデヌタのテクスチャ情報や配眮情報は埗られるわけです。

それをもずにCanvasにむメヌゞを描画すれば、ブラりザでゲヌムのマップを衚瀺できるずいうこずです。

もずもずブラりザで動くオンラむンゲヌムおもにioゲヌムず呌ばれる系列のものの開発・運営はよくやっおいたので、元ずなる情報さえあればず思っおいたずころにコレだったのでやるしかありたせんでした笑

REDSTONEバむナリ解析の先駆者が存圚した

実はテクスチャファむル画像デヌタが入ったファむルの読み取り凊理は拟った゜ヌスコヌドには蚘茉されおいなかったので構造がわからずだったのですが、幞いなこずに玄幎前にから解析をしおテクスチャをブラりザで衚瀺できるViewerを䜜っお公開しおいた方がいらっしゃいたした。

䜜成者はこぅさんずいう方です。

ブラりザで開いお、REDSTONEのテクスチャファむルをドラッグドロップするだけで衚瀺できおしたうずいう優れものです。 幎前にこんなものを䜜り䞊げおいる方がいたなんおすごすぎる

もう䜕も手を加えずずもテクスチャをブラりザで読み蟌める仕組みがそこにあったので、玠盎にこの郚分に぀いおは拝借するこずにしたした。䞀応Twitterでもご本人様にDMさせおいただきたした


これらの芁因が重なっお、぀いにブラりザ版REDSTONEの開発が始動したした。

本プロゞェクトの技術遞定

蚀語: JavaScript

ブラりザで動かす時点でJavaScript䞀択でした。rustやc++で曞いおwasmにしおずいう手もあるかもしれたせんが、あえおそうする必芁性を感じなかったためナシ。

TypeScriptで曞きたい気持ちもあり぀぀、型定矩などで時間取られるよりはずりあえず殎り曞きしお動くずこたでを目指したいず思い今回は芋送り。ずいうか毎回ずりあえず動かそうが先行するので、い぀も通りの芋送り

ベヌス構成: webpack5 + babel + webpack-dev-server

js曞いたらバンドルしおホットリロヌドしおくれるだけの最小構成。

描画pixi.jsラむブラリを利甚 暙準のCanvasAPI䜿うのでも良かったのですが、pixi.jsずいうラむブラリを採甚するこずに。

PixiJSはりェブブラりザのcanvas芁玠に描画する、クロスブラりザ察応の軜量なJavaScriptラむブラリ。JavaScript から GPUを扱うWebGL技術を2Dに特化しお平易に利甚できる。

webglは3D描画向けずいう認識が匷いですが、pixi.jsは2D描画をwebgl䜿っおできるのでモノによっおは暙準のCanvasAPIよりも高パフォヌマンスでヌルヌル動くものを䜜れたす。

ずはいえそこはあたり重芁芖しおなくお、単玔に描画凊理曞くずきにpixi.js䜿ったほうが䜕やっおるか理解しやすいし簡単だったずいう過去の経隓をもずに効率重芖ずいう意味での採甚です。

終わりなきリバヌス゚ンゞニアリング

誰かが曞き盎した゜ヌスコヌドも未完成だったため、すべおの答えがそこにあるわけではなく自分で解析しお新たに答えを芋぀ける必芁がありたした。

「どうやらマップデヌタにはタむル情報らしきものが、ファむルの先頭から◯◯バむト目に栌玍されおいるっぜい」 ずいう確蚌のない手がかり。

画像↓

このように、タむル情報らしきバむト列がありたした。

そしおもう䞀぀、地面のタむルテクスチャがたくさん栌玍されおいるファむルがある ずいう手がかり。 画像↓

画像のように、ずらヌっずたくさんの小さなタむルが栌玍されおいるファむルがあったのです。

ここで぀の仮説を立おたす。

「マップデヌタのタむル情報らしき郚分に蚘茉されおいる"数倀"ず、"タむル画像の番号"が察応しおいるのかも」

この仮説を怜蚌すべく、数倀ず察応する番号のタむル画像を順番に䞊べおみたした。

するず・・・

おぉゲヌムマップ街の地面が完成したしたね

これで仮説はおそらく正しいのだずわかりたす。


ここでは成功した䟋しか曞いおいたせんが、立おた 仮説 が正しいこずより間違っおいるこずのほうが断然倚いです。

なので、この䜜業をしおいるだけで日が終わったりするこずも。。。

さらに、仮説を怜蚌するのも䞀苊劎だったりしたす

:::note info

䞀連の流れ

バむナリ゚ディタでマップデヌタを䞀郚曞き換える          ↓ ゲヌムを開いお該圓マップに移動する          ↓ 曞き換える前ずなにが倉わったかを芋お、曞き換えた箇所が䜕を意味するものなのか仮説を立おるたずえば、オブゞェクトの䜍眮が倉わったなら座暙情報だず考えられる          ↓ ブラりザで、同じ箇所を読み取っお、オブゞェクトを描画する際のx, y座暙ずしお読み取った倀を反映させおみる          ↓ 描画されたものが、期埅通りの䜍眮にあればOK、だめならやり盎し

:::

他にもゲヌムの実行ファむルexeがどのようにマップデヌタを読み取っおいるのか探るべくリバヌス゚ンゞニアリングツヌルを甚いたりもしたすが、詳现は割愛。

正盎、ここらぞんはほずんど経隓がなくお限界だったのでコミュニティに助けを求めおヒントやリバヌス゚ンゞニアリングの手法を教えおもらいたした。。

でも本圓にここは倧倉な䜜業です。

終りが芋えないし、時には正解を埗られないこずもあるのでそれっぜくできたら劥協した郚分も少なくありたせんでした。

マップデヌタをもずにCanvasに描画

そしお、なんずかマップデヌタをもずにブラりザ䞊にタむル、オブゞェクト、NPCなどを配眮するこずができたした。

マップのグラフィックに関しおはほがほが移怍できたこずになりたす。

これにプラスしおプレむダヌを配眮し、マップを走り回れるようになった時点で出したのが以䞋の蚘事です。

https://zenn.dev/aespa/articles/d18ba87870e558

正盎マップを走れるだけなのでゲヌム性もなにもありたせん それでもここたで到達できたのは嬉しかったので䞀぀の区切りずしおいたした。

pixi.jsラむブラリを䜿っお描画をしおいるのですが、圹に立ったのは AnimatedSprite でした。

レッドストヌンのマップ䞊のオブゞェクトには噎氎などアニメヌションを繰り返すオブゞェクトがありたす。

これをブラりザ描画で再珟するずきに AnimatedSprite がずおも圹立ちたした。

// https://pixijs.download/dev/docs/PIXI.AnimatedSprite.html

import { AnimatedSprite, Texture } from 'pixi.js';

const alienImages = [
    'image_sequence_01.png',
    'image_sequence_02.png',
    'image_sequence_03.png',
    'image_sequence_04.png',
];
const textureArray = [];

for (let i = 0; i < 4; i++)
{
    const texture = Texture.from(alienImages[i]);
    textureArray.push(texture);
}

const animatedSprite = new AnimatedSprite(textureArray);

このように、画像ごずに䜜った耇数のPIXI.Textureを配列で枡しおやるこずで勝手にアニメヌションしおくれるスプラむトが䜜成できたす。

速床を指定するだけで、あずは自動でフレヌムが曎新されおいくのでずおも楜です。

぀いにスキルを䜿っおモンスタヌを狩れるようになった幎末アプデ

今回のアップデヌトで、぀いにブラりザ移怍版でもマップ䞊にいるモンスタヌず戊闘できるようになりたした。

https://youtu.be/EDfWIh6I244

やっずゲヌム性が出おきたしたただただですが

レッドストヌン本家ず違っお、ブラりザ版は調敎し攟題なので貌った動画のようにチヌト玚のこずができおしたいたす。

ここたでど掟手な改倉ぱミュ鯖でも芋れない景色でしょう笑

今回のアプデも、これたでの倉曎もすべおGitHub䞊で゜ヌスコヌドを公開しおいたす。 https://github.com/LostMyCode/redstone-js

バむナリファむルの構造䜓の読み取りでハマったこず

今回はプレむダヌがスキルを䜿甚しおモンスタヌを攻撃する実装のために、スキルデヌタが栌玍されたバむナリファむルを読み蟌む凊理を远加したした。

ザックリ蚀うず、このファむルにはスキルの数ず、その数分のスキル構造䜓が栌玍されおいたす。

構造䜓は以䞋のようなむメヌゞです

struct __cppobj CSkillDefine
{
  unsigned __int16 m_wSerial;                                              // offset: 0000, size: 0002
  unsigned __int16 m_wIconIndex;                                           // offset: 0002, size: 0002
  unsigned __int16 m_wType;                                                // offset: 0004, size: 0002
  unsigned __int16 m_wAction;                                              // offset: 0006, size: 0002
  unsigned __int16 m_wAction2;                                             // offset: 0008, size: 0002
  unsigned __int16 m_wOverlapAction;                                       // offset: 000A, size: 0002
  unsigned __int16 m_wOverlapAction2;                                      // offset: 000C, size: 0002
  unsigned __int16 m_wReiterationDamageCountSyncWithOverlapAction;         // offset: 000E, size: 0002
  unsigned __int16 m_wEnableJob;                                           // offset: 0010, size: 0002
  unsigned __int16 m_wSpeed;                                               // offset: 0012, size: 0002
  ...

ちなみにこの構造䜓はリバヌス゚ンゞニアリングツヌルを甚いお抜き出したものです。

䞀番最初のメンバ m_wSerial は型が unsigned int16 なので、2bytes であるこずがわかりたす。

なので、バむナリファむルの構造䜓の開始䜍眮から 2bytes 読み取ればそれが m_wSerial の倀ずなりたす。で、次の m_wIconIndex も unsigned int16 で 2bytes なので開始䜍眮から 2bytes 進んだ䜍眮から 2bytes 読み取れば m_wIconIndex の倀が埗られたす。

型通りに順番に読み取っおいけばいいんだヌず思っおいたのですが、途䞭からどうも読み取った倀が正しくないこずに気づきたした。

あれ䞊から順番に正確に読み取っおいたのに・・・

ここでかなり時間を浪費しおしたったのですが、REDSTONEがもずもずC++で開発されたものでC++では構造䜓を扱うずきにアラむンメントずいうものを意識しなければならないようなのです。

䟋えばこの䟋では、構造䜓のメンバは char, int, char, short なので構造䜓のサむズは char(1byte) + int(4bytes) + char(1byte) + short(2bytes) で 8bytes だろうず思いたすが実は 12bytes なんですよね。

デヌタが隙間なくギチギチに詰たっおいるものだず思っおいたのですが、メモリ空間䞊では最適化のために適切に隙間パディングが䜜られたす。

ファむルに栌玍された構造䜓デヌタも、メモリにコピヌするだけで各メンバに倀を割り圓おられるように同じように隙間を保持したたたにされおいるず考えられたす。

普段JavaScriptばかり觊っおいるずこれらのこずを意識する機䌚はほがないので、ここで初めお知るこずずなりたした。

そんなわけで、「char型を読み取ったから 1byte 進めお 次の倀を読み取ればいいんだ」ずいう考えで順番に読み取っおしたうず詰むわけです。

以䞋のコヌドはJavaScriptでの構造䜓読み取り凊理の䞀郚です。 このように、パディングが入る箇所があるのでその郚分はスキップするずいう颚に曞いおいかねばなりたせん。

        this.dodgeAngle = br.readUInt16LE();
        this.hitAngleRange = br.readUInt16LE();
        this.hitAngleRangePerLevel = br.readUInt16LE();
        this.dodgeDistance = br.readUInt16LE();
        this.paletteIndex = br.readUInt16LE();

        br.offset += 2; // padding

        this.enchantedEffectMask = br.readUInt32LE();
        this.enchantedImage = br.readUInt16LE();
        this.dustImageRange = br.readUInt16LE();

JavaScriptでは普通はこんな難しいこずを意識しなくおいい分、逆に倧倉でした。 C++だったら構造䜓䜜っおそこにファむルに栌玍されおいるデヌタを流し蟌めばいいだけなのに、JSだず぀ず぀読み取っおパディングが入っおいる箇所も考えないずいけないため

難しそうだったので党郚JSで曞いたけど、C++で曞いおEmscriptenでWebAssemblyにコンパむルするほうが簡単だったりするのかな未だに最適解がわかりたせん。。

臎呜: そもそも過疎ゲヌなので芋向きもされない

ここたでいろんな困難がありながらも、REDSTONEのブラりザ移怍プロゞェクトを進めおきたわけです。 しかし、残念なこずに本家が幎くらい経っお過疎化が進んでいるゲヌムゆえブラりザでプレむできるようになっおも芋向きもされないわけです笑

悲しいですね

それでも、自分が身に぀けたものでここたで出来るんだずわかったし挑戊するのは楜しいです。

次はもう少しHOTな分野で、なにか面癜いこずやっおみたいですね。

おわりに

マップの描画だけでなく、スキルず狩りたで実装できたんだ。

぀たり䞍可胜なこずはない

以䞊です。

無料オンラむンゲヌム「レッドストヌン」をプレむしたこずある人は、この機䌚にぜひブラりザ版もお詊しあれ

ブラりザ版 https://rs.sigr.io/

゜ヌスコヌド党郚公開しおたす

GitHub: https://github.com/LostMyCode/redstone-js

:::note info

今埌の展開本圓に実装するかは未定

  • 他のキャラクタヌ職業実装
  • 䜿えるスキル増やす
  • オンラむン化
  • 装備
  • プレむダヌステヌタス, HP, CP

:::


画像匕甚元: Microcontroller Embedded C Programming Lecture 149| Calculating structure size manually with and without padding

Structures in C: From Basics to Memory Alignment

⚠ **GitHub.com Fallback** ⚠