Merge branch 'main' into 1.2.0-dev
2
.github/workflows/build.yml
vendored
|
@ -143,7 +143,7 @@ jobs:
|
||||||
- name: Zip Windows Artifacts
|
- name: Zip Windows Artifacts
|
||||||
uses: vimtor/action-zip@v1.1
|
uses: vimtor/action-zip@v1.1
|
||||||
with:
|
with:
|
||||||
files: geode-win/XInput9_1_0.dll geode-win/Geode.dll geode-win/GeodeUpdater.exe geode-win/Geode.lib
|
files: geode-win/XInput9_1_0.dll geode-win/Geode.dll geode-win/GeodeUpdater.exe geode-win/Geode.lib geode-win/Geode.pdb
|
||||||
dest: geode-${{ steps.ref.outputs.hash }}-win.zip
|
dest: geode-${{ steps.ref.outputs.hash }}-win.zip
|
||||||
|
|
||||||
# TODO change in 2.0.0
|
# TODO change in 2.0.0
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
# Geode Changelog
|
# Geode Changelog
|
||||||
|
|
||||||
|
## v1.1.1
|
||||||
|
* Improve installation confirmation popup (9192769)
|
||||||
|
* Remove unnecessary main thread queues for mod events (38cc38c)
|
||||||
|
* Fix search and filter buttons being not clickable when over the view/restart button of a mod (ef1f1d1)
|
||||||
|
* Improve tab textures (108f56a)
|
||||||
|
* Properly align the borders
|
||||||
|
* Make the selected and unselected tabs the same height
|
||||||
|
|
||||||
## v1.1.0
|
## v1.1.0
|
||||||
* Fix json library not actually being dynamically exported/imported (5f65d97)
|
* Fix json library not actually being dynamically exported/imported (5f65d97)
|
||||||
* Update TulipHook, gets rid of keystone dependency and adds stdcall support (efcbf58, 7b90903)
|
* Update TulipHook, gets rid of keystone dependency and adds stdcall support (efcbf58, 7b90903)
|
||||||
|
|
|
@ -7,6 +7,14 @@ class cocos2d::CCActionTween {
|
||||||
|
|
||||||
[[link(win)]]
|
[[link(win)]]
|
||||||
class cocos2d::CCActionManager {
|
class cocos2d::CCActionManager {
|
||||||
|
CCActionManager() {
|
||||||
|
m_pTargets = nullptr;
|
||||||
|
m_pCurrentTarget = nullptr;
|
||||||
|
m_bCurrentTargetSalvaged = false;
|
||||||
|
}
|
||||||
|
~CCActionManager() {}
|
||||||
|
|
||||||
|
virtual auto update(float) = mac 0x10c9a0;
|
||||||
auto addAction(cocos2d::CCAction*, cocos2d::CCNode*, bool) = mac 0x10bed0;
|
auto addAction(cocos2d::CCAction*, cocos2d::CCNode*, bool) = mac 0x10bed0;
|
||||||
auto pauseTarget(cocos2d::CCObject*) = mac 0x10bc50;
|
auto pauseTarget(cocos2d::CCObject*) = mac 0x10bc50;
|
||||||
auto resumeTargets(cocos2d::CCSet*) = mac 0x10be80;
|
auto resumeTargets(cocos2d::CCSet*) = mac 0x10be80;
|
||||||
|
|
|
@ -1226,6 +1226,9 @@ class EditLevelLayer : cocos2d::CCLayer, FLAlertLayerProtocol, TextInputDelegate
|
||||||
static EditLevelLayer* create(GJGameLevel* level) = mac 0xe1e50, win 0x6f530, ios 0x82420;
|
static EditLevelLayer* create(GJGameLevel* level) = mac 0xe1e50, win 0x6f530, ios 0x82420;
|
||||||
bool init(GJGameLevel* level) = mac 0xe1fd0, win 0x6f5d0;
|
bool init(GJGameLevel* level) = mac 0xe1fd0, win 0x6f5d0;
|
||||||
void onLevelInfo(cocos2d::CCObject*) = mac 0xe4f60, win 0x70660;
|
void onLevelInfo(cocos2d::CCObject*) = mac 0xe4f60, win 0x70660;
|
||||||
|
void onPlay(cocos2d::CCObject*) = mac 0xe3ae0, win 0x71700;
|
||||||
|
void onEdit(cocos2d::CCObject*) = mac 0xe3970, win 0x71ac0;
|
||||||
|
void onShare(cocos2d::CCObject*) = mac 0xe3c60, win 0x71be0;
|
||||||
|
|
||||||
cocos2d::CCMenu* m_buttonMenu;
|
cocos2d::CCMenu* m_buttonMenu;
|
||||||
GJGameLevel* m_level;
|
GJGameLevel* m_level;
|
||||||
|
@ -1447,8 +1450,9 @@ class EditorUI : cocos2d::CCLayer, FLAlertLayerProtocol, ColorSelectDelegate, GJ
|
||||||
PAD = mac 0x2, win 0x2, android 0x2;
|
PAD = mac 0x2, win 0x2, android 0x2;
|
||||||
bool m_freeMoveEnabled;
|
bool m_freeMoveEnabled;
|
||||||
bool m_unkSwipeRelated;
|
bool m_unkSwipeRelated;
|
||||||
PAD = mac 0xa, win 0xa, android 0x9;
|
PAD = mac 0x2, win 0x2, android 0x2;
|
||||||
bool m_updateTimeMarkers;
|
bool m_updateTimeMarkers;
|
||||||
|
PAD = mac 0x8, win 0x8, android 0x2;
|
||||||
cocos2d::CCArray* m_unknownArray2;
|
cocos2d::CCArray* m_unknownArray2;
|
||||||
PAD = mac 0x8, win 0x8, android 0x8;
|
PAD = mac 0x8, win 0x8, android 0x8;
|
||||||
cocos2d::CCArray* m_selectedObjects;
|
cocos2d::CCArray* m_selectedObjects;
|
||||||
|
@ -2330,7 +2334,7 @@ class GJGameLevel : cocos2d::CCNode {
|
||||||
int m_chk;
|
int m_chk;
|
||||||
bool m_isChkValid;
|
bool m_isChkValid;
|
||||||
bool m_isCompletionLegitimate;
|
bool m_isCompletionLegitimate;
|
||||||
geode::SeedValueVRS m_normalPercent;
|
geode::SeedValueVSR m_normalPercent;
|
||||||
geode::SeedValueRSV m_orbCompletion;
|
geode::SeedValueRSV m_orbCompletion;
|
||||||
geode::SeedValueRSV m_newNormalPercent2;
|
geode::SeedValueRSV m_newNormalPercent2;
|
||||||
int m_practicePercent;
|
int m_practicePercent;
|
||||||
|
@ -2597,6 +2601,7 @@ class GJScoreCell : TableViewCell {
|
||||||
void loadFromScore(GJUserScore* score) = win 0x61440;
|
void loadFromScore(GJUserScore* score) = win 0x61440;
|
||||||
void onViewProfile(cocos2d::CCObject* sender) = win 0x62380;
|
void onViewProfile(cocos2d::CCObject* sender) = win 0x62380;
|
||||||
void updateBGColor(int index) = win 0x5c6b0;
|
void updateBGColor(int index) = win 0x5c6b0;
|
||||||
|
GJScoreCell(char const* key, float width, float height) = win 0x613C0;
|
||||||
}
|
}
|
||||||
|
|
||||||
class GJSearchObject : cocos2d::CCNode {
|
class GJSearchObject : cocos2d::CCNode {
|
||||||
|
@ -3761,6 +3766,7 @@ class LevelCell : TableViewCell {
|
||||||
void loadCustomLevelCell() = mac 0x1183b0, win 0x5a020;
|
void loadCustomLevelCell() = mac 0x1183b0, win 0x5a020;
|
||||||
void updateBGColor(int index) = win 0x5c6b0;
|
void updateBGColor(int index) = win 0x5c6b0;
|
||||||
void loadFromLevel(GJGameLevel* level) = win 0x59FD0;
|
void loadFromLevel(GJGameLevel* level) = win 0x59FD0;
|
||||||
|
LevelCell(char const* key, float width, float height) = win 0x59F40;
|
||||||
}
|
}
|
||||||
|
|
||||||
class LevelCommentDelegate {
|
class LevelCommentDelegate {
|
||||||
|
@ -4736,7 +4742,7 @@ class PlayLayer : GJBaseGameLayer, CCCircleWaveDelegate, CurrencyRewardDelegate,
|
||||||
bool unk42C;
|
bool unk42C;
|
||||||
bool m_isPlayer2Frozen;
|
bool m_isPlayer2Frozen;
|
||||||
gd::string m_previousRecords;
|
gd::string m_previousRecords;
|
||||||
double unknown6a8;
|
cocos2d::CCArray* m_replayInputs;
|
||||||
double m_time;
|
double m_time;
|
||||||
int unknown6b8;
|
int unknown6b8;
|
||||||
int unknown6bc;
|
int unknown6bc;
|
||||||
|
@ -4909,12 +4915,12 @@ class PlayerObject : GameObject, AnimatedSpriteDelegate {
|
||||||
void saveToCheckpoint(PlayerCheckpoint*) = mac 0x22e2f0, win 0x1f9ee0;
|
void saveToCheckpoint(PlayerCheckpoint*) = mac 0x22e2f0, win 0x1f9ee0;
|
||||||
void setSecondColor(cocos2d::_ccColor3B const&) = mac 0x219610, win 0x1f7870;
|
void setSecondColor(cocos2d::_ccColor3B const&) = mac 0x219610, win 0x1f7870;
|
||||||
void setupStreak() = mac 0x218720, win 0x1e7e90;
|
void setupStreak() = mac 0x218720, win 0x1e7e90;
|
||||||
void spawnCircle() = mac 0x225480;
|
void spawnCircle() = mac 0x2251b0;
|
||||||
void spawnCircle2() = mac 0x2252a0;
|
void spawnCircle2() = mac 0x2252a0;
|
||||||
void spawnDualCircle() = mac 0x2255c0;
|
void spawnDualCircle() = mac 0x2255c0;
|
||||||
void spawnFromPlayer(PlayerObject*) = mac 0x22dde0, win 0x1f9540;
|
void spawnFromPlayer(PlayerObject*) = mac 0x22dde0, win 0x1f9540;
|
||||||
void spawnPortalCircle(cocos2d::_ccColor3B, float) = mac 0x225350, win 0x1ef680;
|
void spawnPortalCircle(cocos2d::_ccColor3B, float) = mac 0x225350, win 0x1ef680;
|
||||||
void spawnScaleCircle() = mac 0x2251b0, win 0x1ef810;
|
void spawnScaleCircle() = mac 0x225480, win 0x1ef810;
|
||||||
void specialGroundHit() = mac 0x22dbf0;
|
void specialGroundHit() = mac 0x22dbf0;
|
||||||
void speedDown() = mac 0x22e970;
|
void speedDown() = mac 0x22e970;
|
||||||
void speedUp() = mac 0x22e950;
|
void speedUp() = mac 0x22e950;
|
||||||
|
|
|
@ -250,7 +250,7 @@ if (APPLE)
|
||||||
|
|
||||||
add_subdirectory(launcher/mac)
|
add_subdirectory(launcher/mac)
|
||||||
|
|
||||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm" OR GEODE_TARGET_PLATFORM STREQUAL "iOS")
|
if(GEODE_TARGET_PLATFORM STREQUAL "iOS")
|
||||||
add_custom_command(TARGET geode-loader
|
add_custom_command(TARGET geode-loader
|
||||||
POST_BUILD COMMAND
|
POST_BUILD COMMAND
|
||||||
${CMAKE_INSTALL_NAME_TOOL} -id \"/Library/MobileSubstrate/DynamicLibraries/Geode.dylib\"
|
${CMAKE_INSTALL_NAME_TOOL} -id \"/Library/MobileSubstrate/DynamicLibraries/Geode.dylib\"
|
||||||
|
|
|
@ -68,6 +68,8 @@ public:
|
||||||
*/
|
*/
|
||||||
~CCActionManager(void);
|
~CCActionManager(void);
|
||||||
|
|
||||||
|
GEODE_CUSTOM_CONSTRUCTOR_COCOS(CCActionManager, CCObject);
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
|
|
||||||
/** Adds an action with a target.
|
/** Adds an action with a target.
|
||||||
|
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 5.7 KiB |
|
@ -330,7 +330,7 @@ static void handlerThread() {
|
||||||
|
|
||||||
auto text = crashlog::writeCrashlog(faultyMod, getInfo(signalAddress, faultyMod), getStacktrace(), getRegisters());
|
auto text = crashlog::writeCrashlog(faultyMod, getInfo(signalAddress, faultyMod), getStacktrace(), getRegisters());
|
||||||
|
|
||||||
log::error("Geode crashed!\n{}" + text);
|
log::error("Geode crashed!\n{}", text);
|
||||||
|
|
||||||
s_signal = 0;
|
s_signal = 0;
|
||||||
s_cv.notify_all();
|
s_cv.notify_all();
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
#include "../../loader/LoaderImpl.hpp"
|
#include "../../loader/LoaderImpl.hpp"
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include <objc/runtime.h>
|
|
||||||
|
|
||||||
using namespace geode::prelude;
|
using namespace geode::prelude;
|
||||||
|
|
||||||
|
@ -112,26 +111,38 @@ void updateGeode() {
|
||||||
|
|
||||||
extern "C" void fake() {}
|
extern "C" void fake() {}
|
||||||
|
|
||||||
static IMP s_applicationDidFinishLaunching;
|
void applicationDidFinishLaunchingHook(void* self, SEL sel, NSNotification* notification) {
|
||||||
void applicationDidFinishLaunching(id self, SEL sel, NSNotification* notification) {
|
|
||||||
updateGeode();
|
updateGeode();
|
||||||
|
|
||||||
|
std::array<uint8_t, 6> patchBytes = {
|
||||||
|
0x55,
|
||||||
|
0x48, 0x89, 0xe5,
|
||||||
|
0x41, 0x57
|
||||||
|
};
|
||||||
|
|
||||||
|
auto res = tulip::hook::writeMemory((void*)(base::get() + 0x69a0), patchBytes.data(), 6);
|
||||||
|
if (!res)
|
||||||
|
return;
|
||||||
|
|
||||||
int exitCode = geodeEntry(nullptr);
|
int exitCode = geodeEntry(nullptr);
|
||||||
if (exitCode != 0)
|
if (exitCode != 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
using Type = decltype(&applicationDidFinishLaunching);
|
return reinterpret_cast<void(*)(void*, SEL, NSNotification*)>(geode::base::get() + 0x69a0)(self, sel, notification);
|
||||||
return reinterpret_cast<Type>(s_applicationDidFinishLaunching)(self, sel, notification);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool loadGeode() {
|
bool loadGeode() {
|
||||||
Class class_ = objc_getClass("AppController");
|
auto detourAddr = reinterpret_cast<uintptr_t>(&applicationDidFinishLaunchingHook) - geode::base::get() - 0x69a5;
|
||||||
SEL selector = @selector(applicationDidFinishLaunching:);
|
auto detourAddrPtr = reinterpret_cast<uint8_t*>(&detourAddr);
|
||||||
IMP function = (IMP)applicationDidFinishLaunching;
|
|
||||||
using Type = decltype(&applicationDidFinishLaunching);
|
|
||||||
|
|
||||||
s_applicationDidFinishLaunching = class_replaceMethod(class_, selector, function, @encode(Type));
|
std::array<uint8_t, 5> patchBytes = {
|
||||||
|
0xe9, detourAddrPtr[0], detourAddrPtr[1], detourAddrPtr[2], detourAddrPtr[3]
|
||||||
|
};
|
||||||
|
|
||||||
|
auto res = tulip::hook::writeMemory((void*)(base::get() + 0x69a0), patchBytes.data(), 5);
|
||||||
|
if (!res)
|
||||||
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -750,34 +750,75 @@ void IndexItemInfoPopup::onInstallProgress(ModInstallEvent* event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void IndexItemInfoPopup::onInstall(CCObject*) {
|
void IndexItemInfoPopup::onInstall(CCObject*) {
|
||||||
createQuickPopup(
|
auto deps = m_item->getMetadata().getDependencies();
|
||||||
"Confirm Install",
|
enum class DepState {
|
||||||
"Installing this mod requires a few other mods to be installed. "
|
None,
|
||||||
"Would you like to continue with <cy>recommended settings</c> or "
|
HasOnlyRequired,
|
||||||
"<cb>customize</c> which mods to install?",
|
HasOptional
|
||||||
"Customize", "Recommended", 320.f,
|
} depState = DepState::None;
|
||||||
[&](FLAlertLayer*, bool btn2) {
|
for (auto const& item : deps) {
|
||||||
if (btn2) {
|
// resolved means it's already installed, so
|
||||||
auto canInstall = Index::get()->canInstall(m_item);
|
// no need to ask the user whether they want to install it
|
||||||
if (!canInstall) {
|
if (Loader::get()->isModLoaded(item.id))
|
||||||
FLAlertLayer::create(
|
continue;
|
||||||
"Unable to Install",
|
if (item.importance != ModMetadata::Dependency::Importance::Required) {
|
||||||
canInstall.unwrapErr(),
|
depState = DepState::HasOptional;
|
||||||
"OK"
|
break;
|
||||||
)->show();
|
}
|
||||||
return;
|
depState = DepState::HasOnlyRequired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string content;
|
||||||
|
char const* btn1;
|
||||||
|
char const* btn2;
|
||||||
|
switch (depState) {
|
||||||
|
case DepState::None:
|
||||||
|
content = fmt::format(
|
||||||
|
"Are you sure you want to install <cg>{}</c>?",
|
||||||
|
m_item->getMetadata().getName()
|
||||||
|
);
|
||||||
|
btn1 = "Info";
|
||||||
|
btn2 = "Install";
|
||||||
|
break;
|
||||||
|
case DepState::HasOnlyRequired:
|
||||||
|
content =
|
||||||
|
"Installing this mod requires other mods to be installed. "
|
||||||
|
"Would you like to <cy>proceed</c> with the installation or "
|
||||||
|
"<cb>view</c> which mods are going to be installed?";
|
||||||
|
btn1 = "View";
|
||||||
|
btn2 = "Proceed";
|
||||||
|
break;
|
||||||
|
case DepState::HasOptional:
|
||||||
|
content =
|
||||||
|
"This mod recommends installing other mods alongside it. "
|
||||||
|
"Would you like to continue with <cy>recommended settings</c> or "
|
||||||
|
"<cb>customize</c> which mods to install?";
|
||||||
|
btn1 = "Customize";
|
||||||
|
btn2 = "Recommended";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
createQuickPopup("Confirm Install", content, btn1, btn2, 320.f, [&](FLAlertLayer*, bool btn2) {
|
||||||
|
if (btn2) {
|
||||||
|
auto canInstall = Index::get()->canInstall(m_item);
|
||||||
|
if (!canInstall) {
|
||||||
|
FLAlertLayer::create(
|
||||||
|
"Unable to Install",
|
||||||
|
canInstall.unwrapErr(),
|
||||||
|
"OK"
|
||||||
|
)->show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->preInstall();
|
||||||
|
Index::get()->install(m_item);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
InstallListPopup::create(m_item, [&](IndexInstallList const& list) {
|
||||||
this->preInstall();
|
this->preInstall();
|
||||||
Index::get()->install(m_item);
|
Index::get()->install(list);
|
||||||
}
|
})->show();
|
||||||
else {
|
}
|
||||||
InstallListPopup::create(m_item, [&](IndexInstallList const& list) {
|
}, true, true);
|
||||||
this->preInstall();
|
|
||||||
Index::get()->install(list);
|
|
||||||
})->show();
|
|
||||||
}
|
|
||||||
}, true, true
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void IndexItemInfoPopup::preInstall() {
|
void IndexItemInfoPopup::preInstall() {
|
||||||
|
|
|
@ -428,6 +428,9 @@ void ModListLayer::createSearchControl() {
|
||||||
inputBG->setScale(.5f);
|
inputBG->setScale(.5f);
|
||||||
m_searchBG->addChild(inputBG);
|
m_searchBG->addChild(inputBG);
|
||||||
|
|
||||||
|
if (m_searchInput)
|
||||||
|
return;
|
||||||
|
|
||||||
m_searchInput =
|
m_searchInput =
|
||||||
CCTextInputNode::create(310.f - buttonSpace, 20.f, "Search Mods...", "bigFont.fnt");
|
CCTextInputNode::create(310.f - buttonSpace, 20.f, "Search Mods...", "bigFont.fnt");
|
||||||
m_searchInput->setLabelPlaceholderColor({ 150, 150, 150 });
|
m_searchInput->setLabelPlaceholderColor({ 150, 150, 150 });
|
||||||
|
@ -457,10 +460,7 @@ void ModListLayer::reloadList(std::optional<ModListQuery> const& query) {
|
||||||
std::nullopt;
|
std::nullopt;
|
||||||
|
|
||||||
// remove old list
|
// remove old list
|
||||||
if (m_list) {
|
if (m_list) m_list->removeFromParent();
|
||||||
if (m_searchBG) m_searchBG->retain();
|
|
||||||
m_list->removeFromParent();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto items = this->createModCells(g_tab, m_query);
|
auto items = this->createModCells(g_tab, m_query);
|
||||||
|
|
||||||
|
@ -522,13 +522,7 @@ void ModListLayer::reloadList(std::optional<ModListQuery> const& query) {
|
||||||
m_tabsGradientSprite->setPosition(m_list->getPosition() + CCPoint{179.f, 235.f});
|
m_tabsGradientSprite->setPosition(m_list->getPosition() + CCPoint{179.f, 235.f});
|
||||||
|
|
||||||
// add search input to list
|
// add search input to list
|
||||||
if (!m_searchInput) {
|
this->createSearchControl();
|
||||||
this->createSearchControl();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
m_list->addChild(m_searchBG);
|
|
||||||
m_searchBG->release();
|
|
||||||
}
|
|
||||||
|
|
||||||
// enable filter button
|
// enable filter button
|
||||||
m_filterBtn->setEnabled(g_tab != ModListType::Installed);
|
m_filterBtn->setEnabled(g_tab != ModListType::Installed);
|
||||||
|
|
|
@ -11,6 +11,10 @@ GEODE_MEMBER_CHECK(PlayerObject, m_playerColor1, 0x7c2);
|
||||||
|
|
||||||
// EditorUI
|
// EditorUI
|
||||||
GEODE_MEMBER_CHECK(EditorUI, m_buttonBar, 0x1a0);
|
GEODE_MEMBER_CHECK(EditorUI, m_buttonBar, 0x1a0);
|
||||||
|
GEODE_MEMBER_CHECK(EditorUI, m_scaleControl, 0x208);
|
||||||
|
GEODE_MEMBER_CHECK(EditorUI, m_swipeEnabled, 0x23c);
|
||||||
|
GEODE_MEMBER_CHECK(EditorUI, m_updateTimeMarkers, 0x244);
|
||||||
|
GEODE_MEMBER_CHECK(EditorUI, m_selectedObjects, 0x260);
|
||||||
GEODE_MEMBER_CHECK(EditorUI, m_selectedObject, 0x440);
|
GEODE_MEMBER_CHECK(EditorUI, m_selectedObject, 0x440);
|
||||||
|
|
||||||
// LevelEditorLayer
|
// LevelEditorLayer
|
||||||
|
|