Skip to content

Commit

Permalink
fix spawning in Ice Cap 2, clean up implementation
Browse files Browse the repository at this point in the history
(I've had these changes since February 2019)
  • Loading branch information
michael-fadely committed Jun 18, 2020
1 parent 3c0c82a commit dcce47f
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 84 deletions.
2 changes: 1 addition & 1 deletion sadx-mod-loader
203 changes: 122 additions & 81 deletions sadx-test-spawn/mod.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
#include "stdafx.h"

#include <SADXModLoader.h>
#include <shellapi.h> // for CommandLineToArgvW
#include <shellapi.h> // for CommandLineToArgvW
#include <string>
#include <unordered_map>
#include <algorithm>
using std::wstring;
using std::unordered_map;

auto loc_40C318 = (const void*)0x0040C318;
static const auto loc_40C318 = reinterpret_cast<const void*>(0x0040C318);

__declspec(naked) void ForceTrialMode()
{
Expand All @@ -19,90 +17,115 @@ __declspec(naked) void ForceTrialMode()
}
}

static const unordered_map<wstring, uint8_t> levelidsnamemap = {
{ L"hedgehoghammer", LevelIDs_HedgehogHammer },
{ L"emeraldcoast", LevelIDs_EmeraldCoast },
{ L"windyvalley", LevelIDs_WindyValley },
{ L"twinklepark", LevelIDs_TwinklePark },
{ L"speedhighway", LevelIDs_SpeedHighway },
{ L"redmountain", LevelIDs_RedMountain },
{ L"skydeck", LevelIDs_SkyDeck },
{ L"lostworld", LevelIDs_LostWorld },
{ L"icecap", LevelIDs_IceCap },
{ L"casinopolis", LevelIDs_Casinopolis },
{ L"finalegg", LevelIDs_FinalEgg },
{ L"hotshelter", LevelIDs_HotShelter },
{ L"chaos0", LevelIDs_Chaos0 },
{ L"chaos2", LevelIDs_Chaos2 },
{ L"chaos4", LevelIDs_Chaos4 },
{ L"chaos6", LevelIDs_Chaos6 },
{ L"perfectchaos", LevelIDs_PerfectChaos },
{ L"egghornet", LevelIDs_EggHornet },
{ L"eggwalker", LevelIDs_EggWalker },
{ L"eggviper", LevelIDs_EggViper },
{ L"zero", LevelIDs_Zero },
{ L"e101", LevelIDs_E101 },
{ L"e101r", LevelIDs_E101R },
{ L"stationsquare", LevelIDs_StationSquare },
static const std::unordered_map<std::wstring, uint8_t> level_name_ids_map = {
{ L"hedgehoghammer", LevelIDs_HedgehogHammer },
{ L"emeraldcoast", LevelIDs_EmeraldCoast },
{ L"windyvalley", LevelIDs_WindyValley },
{ L"twinklepark", LevelIDs_TwinklePark },
{ L"speedhighway", LevelIDs_SpeedHighway },
{ L"redmountain", LevelIDs_RedMountain },
{ L"skydeck", LevelIDs_SkyDeck },
{ L"lostworld", LevelIDs_LostWorld },
{ L"icecap", LevelIDs_IceCap },
{ L"casinopolis", LevelIDs_Casinopolis },
{ L"finalegg", LevelIDs_FinalEgg },
{ L"hotshelter", LevelIDs_HotShelter },
{ L"chaos0", LevelIDs_Chaos0 },
{ L"chaos2", LevelIDs_Chaos2 },
{ L"chaos4", LevelIDs_Chaos4 },
{ L"chaos6", LevelIDs_Chaos6 },
{ L"perfectchaos", LevelIDs_PerfectChaos },
{ L"egghornet", LevelIDs_EggHornet },
{ L"eggwalker", LevelIDs_EggWalker },
{ L"eggviper", LevelIDs_EggViper },
{ L"zero", LevelIDs_Zero },
{ L"e101", LevelIDs_E101 },
{ L"e101r", LevelIDs_E101R },
{ L"stationsquare", LevelIDs_StationSquare },
{ L"eggcarrieroutside", LevelIDs_EggCarrierOutside },
{ L"eggcarrierinside", LevelIDs_EggCarrierInside },
{ L"mysticruins", LevelIDs_MysticRuins },
{ L"past", LevelIDs_Past },
{ L"twinklecircuit", LevelIDs_TwinkleCircuit },
{ L"skychase1", LevelIDs_SkyChase1 },
{ L"skychase2", LevelIDs_SkyChase2 },
{ L"sandhill", LevelIDs_SandHill },
{ L"ssgarden", LevelIDs_SSGarden },
{ L"ecgarden", LevelIDs_ECGarden },
{ L"mrgarden", LevelIDs_MRGarden },
{ L"chaorace", LevelIDs_ChaoRace }
{ L"eggcarrierinside", LevelIDs_EggCarrierInside },
{ L"mysticruins", LevelIDs_MysticRuins },
{ L"past", LevelIDs_Past },
{ L"twinklecircuit", LevelIDs_TwinkleCircuit },
{ L"skychase1", LevelIDs_SkyChase1 },
{ L"skychase2", LevelIDs_SkyChase2 },
{ L"sandhill", LevelIDs_SandHill },
{ L"ssgarden", LevelIDs_SSGarden },
{ L"ecgarden", LevelIDs_ECGarden },
{ L"mrgarden", LevelIDs_MRGarden },
{ L"chaorace", LevelIDs_ChaoRace }
};

static uint8_t ParseLevelID(const wstring &str)
static uint8_t parse_level_id(const std::wstring& str)
{
wstring str2 = str;
std::transform(str2.begin(), str2.end(), str2.begin(), ::towlower);
auto lv = levelidsnamemap.find(str2);
if (lv != levelidsnamemap.end())
return lv->second;
else
return (uint8_t)wcstol(str.c_str(), nullptr, 10);
std::wstring lowercase = str;
std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(), ::towlower);

const auto it = level_name_ids_map.find(lowercase);

if (it != level_name_ids_map.end())
return it->second;

return static_cast<uint8_t>(std::stol(lowercase));
}

static const unordered_map<wstring, uint8_t> charnamemap = {
{ L"sonic", Characters_Sonic },
{ L"eggman", Characters_Eggman },
{ L"tails", Characters_Tails },
{ L"knuckles", Characters_Knuckles },
{ L"tikal", Characters_Tikal },
{ L"amy", Characters_Amy },
{ L"gamma", Characters_Gamma },
{ L"big", Characters_Big },
static const std::unordered_map<std::wstring, uint8_t> character_name_ids_map = {
{ L"sonic", Characters_Sonic },
{ L"eggman", Characters_Eggman },
{ L"tails", Characters_Tails },
{ L"knuckles", Characters_Knuckles },
{ L"tikal", Characters_Tikal },
{ L"amy", Characters_Amy },
{ L"gamma", Characters_Gamma },
{ L"big", Characters_Big },
{ L"metalsonic", Characters_MetalSonic }
};

static uint8_t ParseCharacterID(const wstring &str)
static uint8_t parse_character_id(const std::wstring& str)
{
wstring s = str;
transform(s.begin(), s.end(), s.begin(), ::towlower);
auto ch = charnamemap.find(s);
if (ch != charnamemap.end())
return ch->second;
else
return (uint8_t)wcstol(str.c_str(), nullptr, 10);
std::wstring lowercase = str;
transform(lowercase.begin(), lowercase.end(), lowercase.begin(), ::towlower);

const auto it = character_name_ids_map.find(lowercase);

if (it != character_name_ids_map.end())
return it->second;

return static_cast<uint8_t>(std::stol(lowercase));
}

static void Obj_Icecap_r(ObjectMaster* o)
{
if (o)
{
MovePlayerToStartPoint(EntityData1Ptrs[0]);
o->MainSub = Obj_Icecap;
Obj_Icecap(o);
}
}

extern "C"
{
__declspec(dllexport) ModInfo SADXModInfo = { ModLoaderVer };
__declspec(dllexport) ModInfo SADXModInfo = {
ModLoaderVer,
nullptr,
nullptr,
0,
nullptr,
0,
nullptr,
0,
nullptr,
0
};

__declspec(dllexport) void __cdecl Init(const char* path, const HelperFunctions& helperFunctions)
{
int argc = 0;
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);

bool level_set = false;
bool act_set = false;
bool act_set = false;

// Prevents CurrentCharacter from being overwritten. There could be other side effects,
// but there are none that I've noticed thus far.
Expand All @@ -112,7 +135,7 @@ extern "C"
{
if (!wcscmp(argv[i], L"--level") || !wcscmp(argv[i], L"-l"))
{
CurrentLevel = ParseLevelID(argv[++i]);
CurrentLevel = parse_level_id(argv[++i]);
PrintDebug("Loading level: %d\n", CurrentLevel);
level_set = true;
}
Expand All @@ -124,52 +147,70 @@ extern "C"
}
else if (!wcscmp(argv[i], L"--character") || !wcscmp(argv[i], L"-c"))
{
auto ch = ParseCharacterID(argv[++i]);
if (ch == Characters_MetalSonic)
uint8_t character_id = parse_character_id(argv[++i]);

if (character_id == Characters_MetalSonic)
{
MetalSonicFlag = 1;
ch = 0;
character_id = 0;
}
CurrentCharacter = ch;

CurrentCharacter = character_id;
PrintDebug("Loading character: %d\n", CurrentCharacter);
}
else if (!wcscmp(argv[i], L"--position") || !wcscmp(argv[i], L"-p"))
{
if (!level_set && !act_set)
{
MessageBoxA(nullptr, "Insufficient arguments for parameter: --position.\n"
"Either --level or --act must be specified before --position.",
"Insufficient arguments", MB_OK);
"Either --level or --act must be specified before --position.",
"Insufficient arguments", MB_OK);

continue;
}

if (i + 3 >= argc)
{
MessageBoxA(nullptr, "Insufficient arguments for parameter: --position.\n"
"All 3 components (X, Y, Z) of the spawn position must be provided. Default spawn point will be used.",
"Insufficient arguments", MB_OK);
"All 3 components (X, Y, Z) of the spawn position must be provided. Default spawn point will be used.",
"Insufficient arguments", MB_OK);

continue;
}

if (CurrentLevel == LevelIDs_IceCap)
{
WriteData<2>(reinterpret_cast<void*>(0x004149EC), 0x90i8);
WriteData<2>(reinterpret_cast<void*>(0x0041497F), 0x90i8);
WriteData<2>(reinterpret_cast<void*>(0x00414A70), 0x90i8);

using LevelObjectFunc = void(__cdecl*)(ObjectMaster* this_);
auto* level_objects = reinterpret_cast<LevelObjectFunc*>(0x0090BF38);
level_objects[LevelIDs_IceCap] = Obj_Icecap_r;
}

const float x = std::stof(argv[++i]);
const float y = std::stof(argv[++i]);
const float z = std::stof(argv[++i]);

StartPosition position = {
CurrentLevel, CurrentAct,
CurrentLevel,
static_cast<int16_t>(CurrentAct),
// Position
{ (float)_wtof(argv[++i]), (float)_wtof(argv[++i]), (float)_wtof(argv[++i]) },
{ x, y, z },
// YRot
0
};

helperFunctions.RegisterStartPosition((Uint8)CurrentCharacter, position);
helperFunctions.RegisterStartPosition(static_cast<Uint8>(CurrentCharacter), position);
}
}

if (level_set || act_set)
{
WriteJump((void*)0x0040C115, ForceTrialMode);
WriteJump(reinterpret_cast<void*>(0x0040C115), ForceTrialMode);
}

LocalFree(argv);
}
}
}
2 changes: 1 addition & 1 deletion sadx-test-spawn/mod.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ Name=Test Spawn
Author=x-hax
Description=Use command-line arguments to load directly into any level and act with any character at any position.
DLLFile=sadx-test-spawn.dll
Version=2.0
Version=2.1
2 changes: 2 additions & 0 deletions sadx-test-spawn/sadx-test-spawn.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>..\sadx-mod-loader\SADXModLoader\include</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_WINDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
Expand All @@ -81,6 +82,7 @@
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>..\sadx-mod-loader\SADXModLoader\include</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_WINDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
Expand Down
4 changes: 3 additions & 1 deletion sadx-test-spawn/stdafx.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <SADXModLoader.h>
#include <shellapi.h> // for CommandLineToArgvW
#include <shellapi.h> // for CommandLineToArgvW
#include <string>
#include <unordered_map>
#include <algorithm>

0 comments on commit dcce47f

Please sign in to comment.