3
0
mirror of https://github.com/CrazyRedMachine/popnhax.git synced 2024-11-27 23:40:50 +01:00
popnhax/popnhax/dllmain.cc
2024-11-17 00:35:54 +01:00

8909 lines
296 KiB
C++

// clang-format off
#include <windows.h>
#include <process.h>
#include <psapi.h>
// clang-format on
#include <vector>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/stat.h>
#include <io.h>
#include <fcntl.h>
#define access _access
#include "popnhax/config.h"
#include "util/membuf.h"
#include "util/log.h"
#include "util/patch.h"
#include "util/xmlprop.hpp"
#include "xmlhelper.h"
#include "translation.h"
#include "custom_categs.h"
#include "omnimix_patch.h"
#include "attract.h"
#include "tachi.h"
#include "tableinfo.h"
#include "loader.h"
#include "SearchFile.h"
#define PROGRAM_VERSION "2.4"
const char *g_game_dll_fn = NULL;
char *g_config_fn = NULL;
FILE *g_log_fp = NULL;
#define DEBUG 0
double g_multiplier = 1.;
DWORD (*real_timeGetTime)();
DWORD patch_timeGetTime()
{
static DWORD last_real = 0;
static DWORD cumul_offset = 0;
if (last_real == 0)
{
last_real = real_timeGetTime()*g_multiplier;
return last_real;
}
DWORD real = real_timeGetTime()*g_multiplier;
DWORD elapsed = real-last_real;
if (elapsed > 16) cumul_offset+= elapsed;
last_real = real;
return real - cumul_offset;
}
bool patch_get_time(double time_rate)
{
if (time_rate == 0)
return true;
g_multiplier = time_rate;
HMODULE hinstLib = GetModuleHandleA("winmm.dll");
_MH_CreateHook((LPVOID)GetProcAddress(hinstLib, "timeGetTime"), (LPVOID)patch_timeGetTime,
(void **)&real_timeGetTime);
LOG("popnhax: time multiplier: %f\n", time_rate);
return true;
}
static void memdump(uint8_t* addr, uint8_t len)
{
LOG("MEMDUMP :\n");
for (int i=0; i<len; i++)
{
LOG("%02x ", *addr);
addr++;
if ((i+1)%16 == 0)
LOG("\n");
}
}
uint8_t *add_string(uint8_t *input);
struct popnhax_config config = {};
PSMAP_BEGIN(config_psmap, static)
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, quick_boot,
"/popnhax/quick_boot")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, hidden_is_offset,
"/popnhax/hidden_is_offset")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, iidx_hard_gauge,
"/popnhax/iidx_hard_gauge")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, show_fast_slow,
"/popnhax/show_fast_slow")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, show_details,
"/popnhax/show_details")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, show_offset,
"/popnhax/show_offset")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, pfree,
"/popnhax/pfree")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, quick_retire,
"/popnhax/quick_retire")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, back_to_song_select,
"/popnhax/back_to_song_select")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, score_challenge,
"/popnhax/score_challenge")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, force_hd_timing,
"/popnhax/force_hd_timing")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_U8, struct popnhax_config, force_hd_resolution,
"/popnhax/force_hd_resolution")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, force_unlocks,
"/popnhax/force_unlocks")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, audio_source_fix,
"/popnhax/audio_source_fix")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, unset_volume,
"/popnhax/unset_volume")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, event_mode,
"/popnhax/event_mode")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, remove_timer,
"/popnhax/remove_timer")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, freeze_timer,
"/popnhax/freeze_timer")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, skip_tutorials,
"/popnhax/skip_tutorials")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, force_full_opt,
"/popnhax/force_full_opt")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, netvs_off,
"/popnhax/netvs_off")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, guidese_off,
"/popnhax/guidese_off")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, patch_db,
"/popnhax/patch_db")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, disable_multiboot,
"/popnhax/disable_multiboot")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_STR, struct popnhax_config, force_patch_xml,
"/popnhax/force_patch_xml")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, patch_xml_dump,
"/popnhax/patch_xml_dump")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, practice_mode,
"/popnhax/practice_mode")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_STR, struct popnhax_config, force_datecode,
"/popnhax/force_datecode")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, network_datecode,
"/popnhax/network_datecode")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, disable_keysounds,
"/popnhax/disable_keysounds")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_S8, struct popnhax_config, keysound_offset,
"/popnhax/keysound_offset")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_S8, struct popnhax_config, audio_offset,
"/popnhax/audio_offset")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_S8, struct popnhax_config, beam_brightness,
"/popnhax/beam_brightness")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, fps_uncap,
"/popnhax/fps_uncap")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, disable_translation,
"/popnhax/disable_translation")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, enhanced_polling,
"/popnhax/enhanced_polling")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_U8, struct popnhax_config, debounce,
"/popnhax/debounce")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, enhanced_polling_stats,
"/popnhax/enhanced_polling_stats")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_S8, struct popnhax_config, enhanced_polling_priority,
"/popnhax/enhanced_polling_priority")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_U8, struct popnhax_config, hispeed_auto,
"/popnhax/hispeed_auto")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_U16, struct popnhax_config, hispeed_default_bpm,
"/popnhax/hispeed_default_bpm")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_S8, struct popnhax_config, base_offset,
"/popnhax/base_offset")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_U8, struct popnhax_config, custom_categ,
"/popnhax/custom_categ")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, exclude_omni,
"/popnhax/exclude_omni")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, partial_entries,
"/popnhax/partial_entries")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_U16, struct popnhax_config, custom_categ_min_songid,
"/popnhax/custom_categ_min_songid")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, custom_exclude_from_version,
"/popnhax/custom_exclude_from_version")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, custom_exclude_from_level,
"/popnhax/custom_exclude_from_level")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_STR, struct popnhax_config, custom_category_title,
"/popnhax/custom_category_title")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_STR, struct popnhax_config, custom_category_format,
"/popnhax/custom_category_format")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_STR, struct popnhax_config, custom_track_title_format,
"/popnhax/custom_track_title_format")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_STR, struct popnhax_config, custom_track_title_format2,
"/popnhax/custom_track_title_format2")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, local_favorites,
"/popnhax/local_favorites")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_STR, struct popnhax_config, local_favorites_path,
"/popnhax/local_favorites_path")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, tachi_scorehook,
"/popnhax/tachi_scorehook")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, tachi_rivals,
"/popnhax/tachi_rivals")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, tachi_scorehook_skip_omni,
"/popnhax/tachi_scorehook_skip_omni")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, ignore_music_limit,
"/popnhax/ignore_music_limit")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, high_framerate,
"/popnhax/high_framerate")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, high_framerate_limiter,
"/popnhax/high_framerate_limiter")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_U16, struct popnhax_config, high_framerate_fps,
"/popnhax/high_framerate_fps")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, autopin,
"/popnhax/autopin")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, attract_ex,
"/popnhax/attract_ex")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, attract_interactive,
"/popnhax/attract_interactive")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, attract_full,
"/popnhax/attract_full")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, attract_lights,
"/popnhax/attract_lights")
PSMAP_MEMBER_REQ(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, force_slow_timer,
"/popnhax/force_slow_timer")
/* removed options are now hidden as optional */
PSMAP_MEMBER_OPT(PSMAP_PROPERTY_TYPE_U8, struct popnhax_config, survival_gauge,
"/popnhax/survival_gauge", 0)
PSMAP_MEMBER_OPT(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, survival_iidx,
"/popnhax/survival_iidx", false)
PSMAP_MEMBER_OPT(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, survival_spicy,
"/popnhax/survival_spicy", false)
PSMAP_MEMBER_OPT(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, disable_expansions,
"/popnhax/disable_expansions", false)
PSMAP_MEMBER_OPT(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, disable_redirection,
"/popnhax/disable_redirection", false)
PSMAP_MEMBER_OPT(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, translation_debug,
"/popnhax/translation_debug", false)
PSMAP_MEMBER_OPT(PSMAP_PROPERTY_TYPE_U16, struct popnhax_config, time_rate,
"/popnhax/time_rate", 0)
PSMAP_MEMBER_OPT(PSMAP_PROPERTY_TYPE_BOOL, struct popnhax_config, extended_debug,
"/popnhax/extended_debug", false)
PSMAP_END
enum BufferIndexes {
MUSIC_TABLE_IDX = 0,
CHART_TABLE_IDX,
STYLE_TABLE_IDX,
FLAVOR_TABLE_IDX,
CHARA_TABLE_IDX,
};
typedef struct {
uint64_t type;
uint64_t method;
uint64_t offset;
uint64_t expectedValue;
uint64_t size;
} UpdateOtherEntry;
typedef struct {
uint64_t type;
uint64_t offset;
uint64_t size;
} UpdateBufferEntry;
typedef struct {
uint64_t method;
uint64_t offset;
} HookEntry;
const size_t LIMIT_TABLE_SIZE = 5;
uint64_t limit_table[LIMIT_TABLE_SIZE] = {0};
uint64_t new_limit_table[LIMIT_TABLE_SIZE] = {0};
uint64_t buffer_addrs[LIMIT_TABLE_SIZE] = {0};
uint8_t *new_buffer_addrs[LIMIT_TABLE_SIZE] = {NULL};
std::vector<UpdateBufferEntry*> buffer_offsets;
std::vector<UpdateOtherEntry*> other_offsets;
std::vector<HookEntry*> hook_offsets;
char *g_datecode_override = NULL;
void (*real_asm_patch_datecode)();
void asm_patch_datecode() {
__asm("push ecx\n");
__asm("push esi\n");
__asm("push edi\n");
__asm("mov ecx,10\n");
__asm("mov esi, %0\n": :"b"((int32_t)g_datecode_override));
__asm("add edi, 6\n");
__asm("rep movsb\n");
__asm("pop edi\n");
__asm("pop esi\n");
__asm("pop ecx\n");
real_asm_patch_datecode();
}
/* retrieve destination address when it checks for "soft/ext", then wait next iteration to overwrite */
uint8_t g_libavs_datecode_patch_state = 0;
char *datecode_property_ptr;
void (*real_asm_patch_datecode_libavs)();
void asm_patch_datecode_libavs() {
if (g_libavs_datecode_patch_state == 2)
__asm("jmp leave_datecode_patch\n");
if (g_libavs_datecode_patch_state == 1)
{
__asm("push edx\n");
__asm("push eax\n");
__asm("push ecx\n");
/* memcpy(datecode_property_ptr, g_datecode_override, 10);*/
__asm("mov edx,_g_datecode_override\n");
__asm("mov eax,_datecode_property_ptr\n");
__asm("mov ecx,dword ptr ds:[edx] \n");
__asm("mov dword ptr ds:[eax],ecx \n");
__asm("mov ecx,dword ptr ds:[edx+4] \n");
__asm("mov dword ptr ds:[eax+4],ecx \n");
__asm("movzx edx,word ptr ds:[edx+8] \n");
__asm("mov word ptr ds:[eax+8],dx \n");
__asm("pop ecx\n");
__asm("pop eax\n");
__asm("pop edx\n");
g_libavs_datecode_patch_state++;
__asm("jmp leave_datecode_patch\n");
}
__asm("mov %0, [esp+0x38]\n":"=a"(datecode_property_ptr):);
if ((uint32_t)datecode_property_ptr > 0x200000 && datecode_property_ptr[0] == 's'&& datecode_property_ptr[5] == 'e'&& datecode_property_ptr[7] == 't')
{
__asm("mov %0, ecx\n":"=c"(datecode_property_ptr):);
g_libavs_datecode_patch_state++;
}
__asm("leave_datecode_patch:\n");
real_asm_patch_datecode_libavs();
}
/* ----------- r2nk226 ----------- */
bool hide_menu = false;
char *g_error_str = NULL;
struct REC {
uint32_t timestamp;
uint8_t judge;
uint8_t button;
uint8_t flag;
uint8_t pad[1];
int32_t timing;
uint32_t recid;
} ;
static REC *recbinArray_loaded = NULL;
static REC *recbinArray_writing = NULL;
uint32_t player_option_offset = 0;
uint32_t **player_options_addr = 0;
uint32_t *button_addr = 0;
uint32_t **usbpad_addr = 0;
uint8_t speed = 5;
uint32_t new_speed = 100;
uint32_t rec_musicid = 0;
uint16_t rec_SPflags = 0;
uint32_t rec_options = 0;
uint8_t rec_hispeed = 10;
uint8_t spec = 0;
uint32_t judge_bar_func = 0;
uint32_t playsramsound_func = 0;
uint32_t demoflag_func = 0;
uint32_t date_func = 0;
uint32_t *p_note = 0;
uint32_t j_win_addr = 0;
uint32_t *input_func = 0;
uint32_t *ran_func = 0;
uint32_t chartbase_addr = 0;
double mul_2dx = 1.;
bool demo_flag = false;
bool r_ran = false;
bool regul_flag = false;
bool is_resultscreen_flag = false;
bool disp = false;
bool use_sp_flag = false;
bool stop_input = true;
bool stop_recchange = true;
bool p_record = false;
bool find_recdata = false;
bool rec_reload = false;
bool recsavefile = false;
/* --------------------------------- */
void (*real_omnimix_patch_jbx)();
void omnimix_patch_jbx() {
__asm("mov %0, byte ptr [edi+4]\n":"=a"(spec): :);
__asm("mov al, 'X'\n");
__asm("mov byte [edi+4], al\n");
real_omnimix_patch_jbx();
}
/*
* auto hi-speed
*/
bool g_longest_bpm_old_chart = false;
bool g_bypass_hispeed = false; //bypass target update for mystery bpm and soflan songs
bool g_mystery_bpm = false;
bool g_soflan_retry = false;
uint32_t g_new_songid = 0; // always contains the latest songid
uint32_t g_last_soflan_songid = 0; // compare with g_new_songid to see if g_soflan_retry_hispeed should apply
uint32_t g_hispeed = 0; // multiplier
uint32_t g_soflan_retry_hispeed = 0; //hispeed value that is temporary kept for quick retry on soflan songs
uint32_t g_hispeed_addr = 0;
uint32_t g_target_bpm = 0;
uint32_t g_default_bpm = 0; //used to rearm between credits
uint16_t g_low_bpm = 0;
uint16_t g_hi_bpm = 0;
uint16_t g_longest_bpm = 0;
uint16_t *g_base_bpm_ptr = &g_hi_bpm; //will point to g_low_bpm or g_longest_bpm according to mode
uint32_t g_low_bpm_ebp_offset = 0;
unsigned char *g_chart_addr = 0;
typedef struct chart_chunk_s {
uint32_t timestamp;
uint16_t operation;
uint16_t data;
uint32_t duration;
} chart_chunk_t;
#define CHART_OP_BPM 0x0445
#define CHART_OP_END 0x0645
typedef struct bpm_list_s {
uint16_t bpm;
uint32_t duration;
struct bpm_list_s *next;
} bpm_list_t;
static uint32_t add_duration(bpm_list_t *list, uint16_t bpm, uint32_t duration)
{
if (list->bpm == 0)
{
list->bpm = bpm;
list->duration = duration;
return duration;
}
while (list->next != NULL)
{
if (list->bpm == bpm)
{
list->duration += duration;
return list->duration;
}
list = list->next;
}
//new entry
bpm_list_t *entry = (bpm_list_t *)malloc(sizeof(bpm_list_t));
entry->bpm = bpm;
entry->duration = duration;
entry->next = NULL;
list->next = entry;
return duration;
}
static void destroy_list(bpm_list_t *list)
{
while (list != NULL)
{
bpm_list_t *tmp = list;
list = list->next;
free(tmp);
}
}
/* the goal is to set g_longest_bpm to the most used bpm from the chart present in memory at address g_chart_addr
*/
void compute_longest_bpm(){
chart_chunk_t* chunk = NULL;
unsigned char *chart_ptr = g_chart_addr;
int chunk_size = g_longest_bpm_old_chart? 8 : 12;
uint32_t prev_timestamp = 0x64; //game adds 0x64 to every timestamp of a chart upon loading
uint16_t prev_bpm = 0;
bpm_list_t *list = (bpm_list_t *)malloc(sizeof(bpm_list_t));
list->bpm = 0;
list->duration = 0;
list->next = NULL;
uint32_t max_duration = 0;
uint16_t longest_bpm = 0;
do {
chunk = (chart_chunk_t*) chart_ptr;
if ( chunk->operation == CHART_OP_BPM || chunk->operation == CHART_OP_END )
{
if (prev_bpm)
{
uint32_t bpm_dur = add_duration(list, prev_bpm, chunk->timestamp - prev_timestamp); //will add to existing entry or create a new one if not present
if (bpm_dur > max_duration)
{
max_duration = bpm_dur;
longest_bpm = prev_bpm;
}
}
prev_bpm = chunk->data;
prev_timestamp = chunk->timestamp;
}
chart_ptr += chunk_size;
} while ( chunk->operation != CHART_OP_END );
destroy_list(list);
g_longest_bpm = longest_bpm;
}
void (*real_rearm_hispeed)();
void hook_rearm_hispeed()
{
__asm("push eax\n");
__asm("mov eax, %0\n"::"m"(g_default_bpm):);
__asm("mov %0, eax\n":"=m"(g_target_bpm): :);
__asm("pop eax\n");
real_rearm_hispeed();
}
void (*real_set_hispeed)();
void hook_set_hispeed()
{
g_bypass_hispeed = false;
__asm("mov %0, eax\n":"=r"(g_hispeed_addr): :);
real_set_hispeed();
}
void (*real_retrieve_chart_addr)();
void hook_retrieve_chart_addr()
{
__asm("push eax\n");
__asm("push ecx\n");
__asm("push edx\n");
__asm("mov %0, esi\n":"=a"(g_chart_addr): :);
__asm("call %0\n"::"r"(compute_longest_bpm));
__asm("pop edx\n");
__asm("pop ecx\n");
__asm("pop eax\n");
real_retrieve_chart_addr();
}
void (*real_retrieve_chart_addr_old)();
void hook_retrieve_chart_addr_old()
{
__asm("push eax\n");
__asm("push ecx\n");
__asm("push edx\n");
__asm("mov %0, edi\n":"=a"(g_chart_addr): :);
__asm("call %0\n"::"r"(compute_longest_bpm));
__asm("pop edx\n");
__asm("pop ecx\n");
__asm("pop eax\n");
real_retrieve_chart_addr_old();
}
/*
* This is called a lot of times: when arriving on option select, and when changing/navigating *any* option
* I'm hooking here to set hi-speed to the target BPM
*/
double g_hispeed_double = 0;
void (*real_read_hispeed)();
void hook_read_hispeed()
{
__asm("push eax\n");
__asm("push ecx\n");
__asm("push edx\n");
__asm("mov ecx, ebp\n");
__asm("add ecx, dword ptr [%0]\n"::"a"(&g_low_bpm_ebp_offset));
__asm("sub ecx, 0x9B4\n");
__asm __volatile__("mov %0, dword ptr [ecx]\n":"=a"(g_new_songid): :);
__asm("add ecx, 0x9B4\n");
__asm __volatile__("mov %0, word ptr [ecx]\n":"=a"(g_low_bpm): :);
__asm("add ecx, 2\n");
__asm __volatile__("mov %0, word ptr [ecx]\n":"=a"(g_hi_bpm): :);
__asm("add ecx, 2\n");
__asm __volatile__("mov %0, byte ptr [ecx]\n":"=a"(g_mystery_bpm): :);
if ( g_soflan_retry
&& ( g_mystery_bpm || g_low_bpm != g_hi_bpm )
&& g_soflan_retry_hispeed
&& ( g_last_soflan_songid == g_new_songid ) )
{
g_hispeed = g_soflan_retry_hispeed;
__asm("jmp apply_hispeed\n");
}
if ( g_bypass_hispeed || g_target_bpm == 0 ) //bypass for mystery BPM and soflan songs once hispeed has been manually changed (to avoid hi-speed being locked since target won't change)
{
__asm("jmp leave_read_hispeed\n");
}
g_hispeed_double = (double)g_target_bpm / (double)(*g_base_bpm_ptr/10.0);
g_hispeed = (uint32_t)(g_hispeed_double+0.5); //rounding to nearest
if (g_hispeed > 0x64) g_hispeed = 0x64;
if (g_hispeed < 0x0A) g_hispeed = 0x0A;
__asm("apply_hispeed:\n");
__asm("and edi, 0xFFFF0000\n"); //keep existing popkun and hidden status values
__asm("or edi, dword ptr[%0]\n"::"m"(g_hispeed)); //fix hispeed initial display on option screen
__asm("mov eax, dword ptr[%0]\n"::"m"(g_hispeed_addr));
__asm("mov dword ptr[eax], edi\n");
__asm("leave_read_hispeed:\n");
__asm("pop edx\n");
__asm("pop ecx\n");
__asm("pop eax\n");
real_read_hispeed();
}
void (*real_increase_hispeed)();
void hook_increase_hispeed()
{
__asm("push eax\n");
__asm("push edx\n");
__asm("push ecx\n");
//increase hispeed
__asm("movzx ecx, word ptr[edi]\n");
__asm("inc ecx\n");
__asm("cmp ecx, 0x65\n");
__asm("jb skip_hispeed_rollover_high\n");
__asm("mov ecx, 0x0A\n");
__asm("skip_hispeed_rollover_high:\n");
if ( g_mystery_bpm || g_low_bpm != g_hi_bpm )
{
__asm("mov %0, ecx\n":"=m"(g_soflan_retry_hispeed):);
g_soflan_retry = false;
g_bypass_hispeed = true;
g_last_soflan_songid = g_new_songid&0xFFFF;
__asm("jmp leave_increase_hispeed\n");
}
//compute resulting bpm using exact same formula as game (base bpm in eax, multiplier in ecx)
__asm("movsx eax, %0\n"::"m"(g_hi_bpm));
__asm("cwde\n");
__asm("movsx ecx,cx\n");
__asm("imul ecx,eax\n");
__asm("mov eax, 0x66666667\n");
__asm("imul ecx\n");
__asm("sar edx,2\n");
__asm("mov eax,edx\n");
__asm("shr eax,0x1F\n");
__asm("add eax,edx\n");
__asm("mov %0, eax\n":"=m"(g_target_bpm): :);
__asm("leave_increase_hispeed:\n");
__asm("pop ecx\n");
__asm("pop edx\n");
__asm("pop eax\n");
real_increase_hispeed();
}
void (*real_decrease_hispeed)();
void hook_decrease_hispeed()
{
__asm("push eax\n");
__asm("push edx\n");
__asm("push ecx\n");
//decrease hispeed
__asm("movzx ecx, word ptr[edi]\n");
__asm("dec ecx\n");
__asm("cmp ecx, 0x0A\n");
__asm("jge skip_hispeed_rollover_low\n");
__asm("mov ecx, 0x64\n");
__asm("skip_hispeed_rollover_low:\n");
if ( g_mystery_bpm || g_low_bpm != g_hi_bpm )
{
__asm("mov %0, ecx\n":"=m"(g_soflan_retry_hispeed):);
g_soflan_retry = false;
g_bypass_hispeed = true;
g_last_soflan_songid = g_new_songid&0xFFFF;
__asm("jmp leave_decrease_hispeed\n");
}
//compute resulting bpm using exact same formula as game (base bpm in eax, multiplier in ecx)
__asm("movsx eax, %0\n"::"m"(g_hi_bpm));
__asm("cwde\n");
__asm("movsx ecx,cx\n");
__asm("imul ecx,eax\n");
__asm("mov eax, 0x66666667\n");
__asm("imul ecx\n");
__asm("sar edx,2\n");
__asm("mov eax,edx\n");
__asm("shr eax,0x1F\n");
__asm("add eax,edx\n");
__asm("mov %0, eax\n":"=m"(g_target_bpm): :);
__asm("leave_decrease_hispeed:\n");
__asm("pop ecx\n");
__asm("pop edx\n");
__asm("pop eax\n");
real_decrease_hispeed();
}
void (*real_leave_options)();
void retry_soflan_reset()
{
g_soflan_retry = false;
real_leave_options();
}
bool patch_hispeed_auto(uint8_t mode)
{
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
g_base_bpm_ptr = &g_hi_bpm;
if (mode == 2)
g_base_bpm_ptr = &g_low_bpm;
else if (mode == 3)
g_base_bpm_ptr = &g_longest_bpm;
g_target_bpm = g_default_bpm;
/* reset target to default bpm at the end of a credit */
{
int64_t pattern_offset = _search(data, dllSize, "\x8B\x10\x8B\xC8\x8B\x42\x28\xFF\xE0\xCC", 10, 0);
if (pattern_offset == -1) {
LOG("WARNING: popnhax: auto hi-speed: cannot find playerdata clean function\n");
return false;
} else {
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_rearm_hispeed,
(void **)&real_rearm_hispeed);
}
}
/* retrieve hi-speed address */
{
int64_t pattern_offset = _search(data, dllSize, "\x66\x89\x0C\x07\x0F\xB6\x45\x04", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: auto hi-speed: cannot find hi-speed address\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x04;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_set_hispeed,
(void **)&real_set_hispeed);
}
/* write new hispeed according to target bpm */
{
/* improve compatibility with newer games */
int64_t pattern_offset = _search(data, dllSize, "\x0B\x00\x83\xC4\x04\xEB\x57\x8B\xBC\x24", 10, 0);
if (pattern_offset == -1) {
LOG("popnhax: auto hi-speed: cannot find chart BPM address offset\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x21;
g_low_bpm_ebp_offset = *((uint16_t *)(patch_addr));
if (g_low_bpm_ebp_offset != 0x0A1A && g_low_bpm_ebp_offset != 0x0A1E)
{
LOG("popnhax: auto hi-speed: WARNING: unexpected BPM address offset (%hu), might not work\n", g_low_bpm_ebp_offset);
}
pattern_offset = _search(data, dllSize, "\x98\x50\x66\x8B\x85", 5, 0);
if (pattern_offset == -1) {
LOG("popnhax: auto hi-speed: cannot find hi-speed apply address\n");
return false;
}
patch_addr = (int64_t)data + pattern_offset - 0x07;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_read_hispeed,
(void **)&real_read_hispeed);
}
/* update target bpm on hispeed increase */
{
int64_t pattern_offset = _search(data, dllSize, "\x66\xFF\x07\x0F\xB7\x07\x66\x83\xF8\x64", 10, 0);
if (pattern_offset == -1) {
LOG("popnhax: auto hi-speed: cannot find hi-speed increase\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_increase_hispeed,
(void **)&real_increase_hispeed);
}
/* update target bpm on hispeed decrease */
{
int64_t pattern_offset = _search(data, dllSize, "\x66\xFF\x0F\x0F\xB7\x07\x66\x83\xF8\x0A", 10, 0);
if (pattern_offset == -1) {
LOG("popnhax: auto hi-speed: cannot find hi-speed decrease\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_decrease_hispeed,
(void **)&real_decrease_hispeed);
}
/* set g_soflan_retry back to false when leaving options */
{
int8_t delta = 0;
int64_t first_loc = _search(data, dllSize, "\x0A\x00\x00\x83\xC0\x04\xBF\x0C\x00\x00\x00\xE8", 12, 0);
if (first_loc == -1) {
LOG("popnhax: auto hi-speed: cannot retrieve option screen loop function\n");
return false;
}
int64_t pattern_offset = _search(data, 1000, "\x33\xC9\x51\x50\x8B", 5, first_loc);
if (pattern_offset == -1) {
pattern_offset = _search(data, 1000, "\x74\x1A\x8B\x85\x14\x0A\x00\x00\x53\x6A", 10, first_loc); // popn28
delta = 0x23;
if (pattern_offset == -1)
{
LOG("popnhax: auto hi-speed: cannot retrieve option screen leave\n");
return false;
}
}
uint64_t patch_addr = (int64_t)data + pattern_offset + delta;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)retry_soflan_reset,
(void **)&real_leave_options);
}
/* compute longest bpm for mode 3 */
if (mode == 3)
{
int64_t pattern_offset = _search(data, dllSize, "\x00\x00\x72\x05\xB9\xFF", 6, 0);
if (pattern_offset == -1) {
LOG("popnhax: auto hi-speed: cannot find chart address\n");
return false;
}
/* detect if usaneko+ */
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x0F;
uint8_t check_byte = *((uint8_t *)(patch_addr + 1));
if (check_byte == 0x04)
{
patch_addr += 9;
g_longest_bpm_old_chart = true;
LOG("popnhax: auto hi-speed: old game version\n");
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_retrieve_chart_addr_old,
(void **)&real_retrieve_chart_addr_old);
}
else
{
patch_addr += 11;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_retrieve_chart_addr,
(void **)&real_retrieve_chart_addr);
}
}
LOG("popnhax: auto hi-speed enabled");
if (g_target_bpm != 0)
LOG(" with default %hubpm", g_target_bpm);
if (mode == 2)
LOG(" (lower bpm target)");
else if (mode == 3)
LOG(" (longest bpm target)");
else
LOG(" (higher bpm target)");
LOG("\n");
return true;
}
/* dummy function to replace real one when not found */
bool is_normal_mode_best_effort(){
return true;
}
bool (*popn22_is_normal_mode)() = is_normal_mode_best_effort;
uint32_t g_startsong_addr = 0;
uint32_t g_transition_addr = 0;
uint32_t g_stage_addr = 0;
uint32_t g_score_addr = 0;
bool g_pfree_mode = false;
bool g_end_session = false;
bool g_return_to_options = false;
bool g_return_to_song_select = false;
bool g_return_to_song_select_num9 = false;
void (*real_screen_transition)();
void quickexit_screen_transition()
{
if (g_return_to_options)
{
__asm("mov dword ptr [edi+0x30], 0x1C\n");
//flag is set back to false in the option select screen after score cleanup
}
else if (g_return_to_song_select)
{
__asm("mov dword ptr [edi+0x30], 0x17\n");
__asm("push eax");
__asm("call %0"::"a"(popn22_is_normal_mode));
__asm("test al,al");
__asm("pop eax");
__asm("je skip_change_flag");
if ( g_pfree_mode )
{
g_return_to_song_select = false;
}
//flag is set back to false in hook_stage_increment otherwise
}
__asm("skip_change_flag:");
g_end_session = false;
real_screen_transition();
}
void (*real_retrieve_score)();
void quickretry_retrieve_score()
{
__asm("mov %0, esi\n":"=S"(g_score_addr): :);
/* let's force E and fail medal for quick exit
* (regular retire or end of song will overwrite)
*/
uint8_t *clear_type = (uint8_t*)(g_score_addr+0x24);
uint8_t *clear_rank = (uint8_t*)(g_score_addr+0x25);
if (*clear_type == 0)
{
*clear_type = 1;
*clear_rank = 1;
}
real_retrieve_score();
}
void quickexit_option_screen_cleanup()
{
if (g_return_to_options)
{
/* we got here from quickretry, cleanup score */
__asm("push ecx\n");
__asm("push esi\n");
__asm("push edi\n");
__asm("mov edi, %0\n": :"D"(g_score_addr));
__asm("mov esi, edi\n");
__asm("add esi, 0x560\n");
__asm("mov ecx, 0x98\n");
__asm("rep movsd\n");
__asm("pop edi\n");
__asm("pop esi\n");
__asm("pop ecx\n");
g_return_to_options = false;
}
}
uint8_t g_skip_options = 0;
uint32_t loadnew2dx_func;
uint32_t playgeneralsound_func;
char *g_system2dx_filepath;
uint32_t g_addr_icca;
void (*real_option_screen)();
void quickexit_option_screen()
{
/* r2nk226
for record reloading */
if (rec_reload) {
goto reload;
}
quickexit_option_screen_cleanup();
__asm("push ebx\n");
__asm("push ecx\n");
__asm("mov ecx, %0\n": :"m"(g_addr_icca));
__asm("mov ebx, [ecx]\n");
__asm("pop ecx\n");
__asm("add ebx, 0xAC\n");
__asm("mov ebx, [ebx]\n");
__asm("shr ebx, 28\n"); // numpad8: 80 00 00 00
__asm("cmp bl, 8\n");
__asm("pop ebx\n");
__asm("jne real_option_screen\n");
/* numpad 8 is held, set a flag to rewrite transition pointer later */
reload:
__asm("mov %0, 1\n":"=m"(g_skip_options):);
__asm("real_option_screen:\n");
real_option_screen();
}
void (*real_option_screen_apply_skip)();
void quickexit_option_screen_apply_skip()
{
__asm("cmp byte ptr[_g_skip_options], 0\n");
__asm("je real_option_screen_no_skip\n");
/* flag is set, rewrite transition pointer */
__asm("pop ecx\n");
__asm("pop edx\n");
__asm("xor edx, edx\n");
__asm("mov ecx, %0\n": :"c"(g_startsong_addr));
__asm("push edx\n");
__asm("push ecx\n");
__asm("mov %0, 0\n":"=m"(g_skip_options):);
__asm("real_option_screen_no_skip:\n");
real_option_screen_apply_skip();
}
uint32_t g_transition_offset = 0xA10;
/* because tsumtsum doesn't go through the second transition, try to recover from a messup here */
void (*real_option_screen_apply_skip_tsum)();
void quickexit_option_screen_apply_skip_tsum()
{
__asm("cmp byte ptr[_g_skip_options], 0\n");
__asm("je real_option_screen_no_skip_tsum\n");
/* flag is set, rewrite transition pointer */
__asm("push ebx\n");
__asm("push ecx\n");
__asm("mov ebx, ecx\n");
__asm("add ebx, [_g_transition_offset]\n");
__asm("mov ebx, [ebx]\n");
__asm("add ebx, 8\n");
__asm("mov ecx, %0\n": :"c"(g_startsong_addr));
__asm("mov [ebx], ecx\n");
__asm("pop ecx\n");
__asm("pop ebx\n");
__asm("mov %0, 0\n":"=m"(g_skip_options):);
__asm("real_option_screen_no_skip_tsum:\n");
real_option_screen_apply_skip_tsum();
}
uint8_t g_srambypass = 0;
void (*real_option_screen_later)();
void backtosongselect_option_screen()
{
/* cannot use backtosongselect when not in normal mode */
__asm("push eax");
__asm("call %0"::"a"(popn22_is_normal_mode));
__asm("test al,al");
__asm("pop eax");
__asm("je exit_back_select");
__asm("push ecx\n");
__asm("mov ecx, %0\n": :"m"(g_addr_icca));
__asm("mov ebx, [ecx]\n");
__asm("pop ecx\n");
__asm("add ebx, 0xAC\n");
__asm("mov ebx, [ebx]\n");
__asm("shr ebx, 16\n"); // numpad9: 00 08 00 00
__asm("cmp bl, 8\n");
__asm("jne exit_back_select\n");
g_return_to_song_select = true;
g_return_to_song_select_num9 = true;
/* reload correct .2dx file to get correct generalSounds */
__asm("push eax\n");
__asm("push ecx\n");
__asm("push edx\n");
__asm("push %0\n"::"m"(g_system2dx_filepath));
__asm("call %0\n"::"D"(loadnew2dx_func));
__asm("add esp, 4\n");
/* play exit sound */
__asm("push 0\n");
__asm("push 0x19\n");
__asm("call %0\n"::"D"(playgeneralsound_func));
__asm("add esp, 8\n");
/* set srambypass flag */
__asm("mov %0, 1\n":"=m"(g_srambypass):);
__asm("pop edx\n");
__asm("pop ecx\n");
__asm("pop eax\n");
__asm("exit_back_select:\n");
real_option_screen_later();
}
void (*real_backtosongselect_herewego1)();
void backtosongselect_herewego1()
{
__asm("cmp %0, 1\n"::"m"(g_srambypass));
__asm("jne skip_disable_herewego1\n");
__asm("mov %0, 0\n":"=m"(g_srambypass):);
__asm("xor eax, eax\n");
__asm("skip_disable_herewego1:\n");
real_backtosongselect_herewego1();
}
void (*real_backtosongselect_herewego2)();
void backtosongselect_herewego2()
{
__asm("cmp %0, 1\n"::"m"(g_srambypass));
__asm("jne skip_disable_herewego2\n");
__asm("mov %0, 0\n":"=m"(g_srambypass):);
__asm("xor eax, eax\n");
__asm("skip_disable_herewego2:\n");
real_backtosongselect_herewego2();
}
void (*real_backtosongselect_herewego3)();
void backtosongselect_herewego3()
{
__asm("cmp %0, 1\n"::"m"(g_srambypass));
__asm("jne skip_disable_herewego3\n");
__asm("mov %0, 0\n":"=m"(g_srambypass):);
__asm("xor eax, eax\n");
__asm("skip_disable_herewego3:\n");
real_backtosongselect_herewego3();
}
void (*real_backtosongselect_option_screen_auto_leave)();
void backtosongselect_option_screen_auto_leave()
{
if ( g_return_to_song_select_num9 )
{
g_return_to_song_select_num9 = false;
__asm("mov al, 1\n");
}
real_backtosongselect_option_screen_auto_leave();
}
void (*real_option_screen_yellow)();
void backtosongselect_option_yellow()
{
/* cannot use backtosongselect when not in normal mode */
__asm("push eax");
__asm("call %0"::"a"(popn22_is_normal_mode));
__asm("test al,al");
__asm("pop eax");
__asm("je exit_back_select_yellow");
__asm("push ecx\n");
__asm("mov ecx, %0\n": :"m"(g_addr_icca));
__asm("mov ebx, [ecx]\n");
__asm("pop ecx\n");
__asm("add ebx, 0xAC\n");
__asm("mov ebx, [ebx]\n");
__asm("shr ebx, 16\n"); // numpad9: 00 08 00 00
__asm("cmp bl, 8\n");
__asm("jne exit_back_select_yellow\n");
g_return_to_song_select = true;
g_return_to_song_select_num9 = true;
/* reload correct .2dx file to get correct generalSounds */
__asm("push eax\n");
__asm("push ecx\n");
__asm("push edx\n");
__asm("push %0\n"::"m"(g_system2dx_filepath));
__asm("call %0\n"::"D"(loadnew2dx_func));
__asm("add esp, 4\n");
/* play exit sound */
__asm("push 0\n");
__asm("push 0x19\n");
__asm("call %0\n"::"D"(playgeneralsound_func));
__asm("add esp, 8\n");
/* set srambypass flag */
__asm("mov %0, 1\n":"=m"(g_srambypass):);
__asm("pop edx\n");
__asm("pop ecx\n");
__asm("pop eax\n");
__asm("exit_back_select_yellow:\n");
real_option_screen_yellow();
}
uint32_t g_option_yellow_leave_addr = 0;
void (*real_backtosongselect_option_screen_yellow_auto_leave)();
void backtosongselect_option_screen_yellow_auto_leave()
{
if ( g_return_to_song_select_num9 )
{
g_return_to_song_select_num9 = false;
__asm("push %0\n": :"m"(g_option_yellow_leave_addr));
__asm("ret\n");
}
real_backtosongselect_option_screen_yellow_auto_leave();
}
void get_recPlayoptions() {
uint32_t *g_options_addr;
uint32_t *g_musicid_addr;
uint8_t *g_rechispeed_addr;
uint8_t stage_no = 0;
uint32_t option_offset = 0;
stage_no = *(uint8_t*)(**player_options_addr +0x0E);
option_offset = player_option_offset * stage_no;
g_musicid_addr = (uint32_t*)(**player_options_addr +0x20 +option_offset);
rec_musicid = *g_musicid_addr;
g_options_addr = (uint32_t*)(**player_options_addr +0x34 +option_offset);
rec_options = *g_options_addr;
g_rechispeed_addr = (uint8_t*)(**player_options_addr +0x2A +option_offset);
rec_hispeed = (uint8_t)*g_rechispeed_addr;
rec_SPflags = (new_speed << 8) | ((uint8_t)regul_flag << 4) | speed;
}
void save_recSPflags() {
uint32_t *g_coolgreat_addr;
uint32_t *g_goodbad_addr;
uint32_t val = 0;
uint8_t stage_no = 0;
uint8_t shift = 0;
uint32_t option_offset = 0;
uint32_t size = LOWORD(recbinArray_writing[0].timestamp);
if (config.game_version == 24) {
rec_options ^= 0x01000000; // guidese_flag reverse
} else if (config.game_version == 28) {
shift = 8;
}
stage_no = *(uint8_t*)(**player_options_addr +0x0E);
option_offset = player_option_offset * stage_no;
g_coolgreat_addr = (uint32_t*)(**player_options_addr +0x64 +option_offset +shift);
g_goodbad_addr = (uint32_t*)(**player_options_addr +0x68 +option_offset +shift);
recbinArray_writing[0].timestamp |= ((uint32_t)rec_SPflags << 16);
uint32_t count_check = *g_coolgreat_addr + *g_goodbad_addr;
count_check = LOWORD(count_check) + HIWORD(count_check);
if (count_check != size) {
uint32_t check_cool = 0;
uint32_t check_great = 0;
uint32_t check_good = 0;
uint32_t check_bad = 0;
for (uint32_t i=0; i < size; i++) {
if (recbinArray_writing[i+2].judge == 2) {
check_cool++;
} else if ((recbinArray_writing[i+2].judge == 3) || (recbinArray_writing[i+2].judge == 4)) {
check_great++;
} else if ((recbinArray_writing[i+2].judge == 5) || (recbinArray_writing[i+2].judge == 6)) {
check_good++;
} else if ((recbinArray_writing[i+2].judge == 1) || (recbinArray_writing[i+2].judge == 7) || (recbinArray_writing[i+2].judge == 8) || (recbinArray_writing[i+2].judge == 9)) {
check_bad++;
}
}
uint32_t cg = (check_great << 16) | check_cool;
uint32_t gb = (check_bad << 16) | check_good;
recbinArray_writing[0].recid = cg;
recbinArray_writing[0].timing = gb;
val = cg ^ gb ^ recbinArray_writing[0].timestamp;
} else {
recbinArray_writing[0].recid = *g_coolgreat_addr;
recbinArray_writing[0].timing = *g_goodbad_addr;
val = *g_coolgreat_addr ^ *g_goodbad_addr ^ recbinArray_writing[0].timestamp;
}
val = ~(val ^ 0x672DE);
recbinArray_writing[0].judge = LOBYTE(val);
recbinArray_writing[0].button = (uint8_t)(LOWORD(val) >> 8);
recbinArray_writing[0].flag = (uint8_t)(HIWORD(val) & 0xFF);
recbinArray_writing[0].pad[0] = (uint8_t)(HIWORD(val) >> 8);
recbinArray_writing[1].timestamp = rec_options;
recbinArray_writing[1].judge = rec_hispeed;
recbinArray_writing[1].button = (uint8_t)~recbinArray_writing[0].button;
recbinArray_writing[1].flag = (uint8_t)~recbinArray_writing[0].flag;
uint16_t *button_no = *(uint16_t **)button_addr;
uint8_t bt00 = *button_no;
uint32_t bt14 = 0;
uint32_t bt58 = 0;
int8_t i = 0;
do
{
button_no++;
bt14 |= *button_no << (i*8);
++i;
} while (i < 4);
i=0;
do
{
button_no++;
bt58 |= *button_no << (i*8);
++i;
} while (i < 4);
printf("bt00(%02x) bt14(%08X) bt58(%08X)\n", bt00, bt14, bt58);
recbinArray_writing[1].pad[0] = bt00;
recbinArray_writing[1].timing = bt14;
recbinArray_writing[1].recid = bt58;
/* .rec file format
random,timing,gauge,guide_se,speed,(00),(00),bt_0-bt_8 */
// after saving, restore original options if r_ran = true
if (r_ran) {
uint8_t *g_options_addr;
g_options_addr = (uint8_t*)(**player_options_addr +0x34 +option_offset);
*g_options_addr = (uint8_t)rec_options;
}
}
bool sp_reset = false;
void practice_scoremedal_reset() {
/* clear scores and medals when use_sp_flag = true */
if (use_sp_flag) {
__asm("push ecx\n");
__asm("push esi\n");
__asm("push edi\n");
__asm("mov edi, %0\n": :"D"(g_score_addr));
__asm("mov esi, edi\n");
__asm("add esi, 0x560\n");
__asm("mov ecx, 0x98\n");
__asm("rep movsd\n");
__asm("pop edi\n");
__asm("pop esi\n");
__asm("pop ecx\n");
}
sp_reset = true;
}
void (*real_option_screen_simple)();
void quickexit_option_screen_simple() {
/* for record reloading */
if (rec_reload) {
g_return_to_options = false;
/* rewrite transition pointer */
//__asm("pop edi\n");
__asm("pop ecx\n");
__asm("pop edx\n");
__asm("xor edx, edx\n");
__asm("mov ecx, %0\n"::"c"(g_startsong_addr));
__asm("push edx\n");
__asm("push ecx\n");
//__asm("push edi\n");
}
real_option_screen_simple();
}
/* --------------------------------- */
/*
numpad values:
| <<24 <<28 <<16
---+---------------
8 | 7 8 9
4 | 4 5 6
2 | 1 2 3
1 | 0 00
e.g. numpad 9 = 8<<16 = 00 08 00 00
numpad 2 = 2<<28 = 20 00 00 00
*/
void (*real_game_loop)();
void quickexit_game_loop()
{
__asm("push ebx\n");
__asm("push ecx\n");
__asm("mov ecx, %0\n": :"m"(g_addr_icca));
__asm("mov ebx, [ecx]\n");
__asm("pop ecx\n");
__asm("add ebx, 0xAC\n");
__asm("mov ebx, [ebx]\n");
__asm("shr ebx, 16\n"); // numpad9: 00 08 00 00
__asm("cmp bl, 8\n");
__asm("je leave_song\n");
__asm("shr ebx, 12\n"); // (adds to the previous shr 16) numpad8: 08 00 00 00
__asm("cmp bl, 8\n");
__asm("jne call_real\n");
/* numpad 8 is pressed: quick retry if pfree is active */
__asm("push eax");
__asm("call %0"::"a"(popn22_is_normal_mode));
__asm("test al,al");
__asm("pop eax");
__asm("je skip_pfree_check");
if ( !g_pfree_mode )
{
__asm("skip_pfree_check:");
__asm("jmp call_real\n");
}
else
{
if (g_mystery_bpm || g_low_bpm != g_hi_bpm)
g_soflan_retry = true;
}
g_return_to_options = true;
/* numpad 8 or 9 is pressed */
__asm("leave_song:\n");
__asm("mov eax, 1\n");
__asm("pop ebx\n");
__asm("ret\n");
/* no quick exit */
__asm("call_real:\n");
__asm("pop ebx\n");
real_game_loop();
}
int16_t g_keysound_offset = 0;
void (*real_eval_timing)();
void patch_eval_timing() {
__asm("mov esi, %0\n": :"b"((int32_t)g_keysound_offset));
real_eval_timing();
}
uint32_t g_last_playsram = 0;
void (*real_result_loop)();
void quickexit_result_loop()
{
__asm("push ecx\n");
__asm("mov ecx, %0\n": :"m"(g_addr_icca));
__asm("mov ebx, [ecx]\n");
__asm("pop ecx\n");
__asm("add ebx, 0xAC\n");
__asm("mov ebx, [ebx]\n");
__asm("shr ebx, 16\n"); // numpad9: 00 08 00 00
__asm("cmp bl, 8\n");
__asm("je quit_session\n");
__asm("shr ebx, 12\n"); // (adds to the previous shr 16) numpad8: 08 00 00 00
__asm("cmp bl, 8\n");
__asm("jne call_real_result\n");
__asm("push eax");
__asm("call %0"::"a"(popn22_is_normal_mode));
__asm("test al,al");
__asm("pop eax");
__asm("je skip_quickexit_pfree_check");
if ( !g_pfree_mode )
{
__asm("skip_quickexit_pfree_check:");
__asm("jmp call_real_result\n");
}
else
{
if (g_mystery_bpm || g_low_bpm != g_hi_bpm)
g_soflan_retry = true;
}
g_return_to_options = true; //transition screen hook will catch it
__asm("push eax\n");
__asm("push ecx\n");
__asm("push edx\n");
__asm("call _timeGetTime@0\n");
__asm("sub eax, [_g_last_playsram]\n");
__asm("cmp eax, 10000\n"); //10sec cooldown
__asm("pop edx\n");
__asm("pop ecx\n");
__asm("ja play_soundfx_retry\n"); //skip playing sound if cooldown value not reached
__asm("pop eax\n");
__asm("jmp call_real_result\n");
__asm("play_soundfx_retry:\n");
__asm("add [_g_last_playsram], eax"); // place curr_time in g_last_playsram (cancel sub eax, [_g_last_playsram])
__asm("pop eax\n");
/* play sound fx (retry song) */
__asm("push eax\n");
__asm("push ecx\n");
__asm("push edx\n");
__asm("mov eax, 0x16\n"); //"okay" sound fx
__asm("push 0\n");
__asm("call %0\n"::"D"(playsramsound_func));
__asm("add esp, 4\n");
__asm("pop edx\n");
__asm("pop ecx\n");
__asm("pop eax\n");
__asm("jmp call_real_result\n");
__asm("quit_session:\n");
g_end_session = true;
g_return_to_options = false;
/* set value 5 in g_stage_addr and -4 in g_transition_addr (to get fade to black transition) */
__asm("mov ebx, %0\n": :"b"(g_stage_addr));
__asm("mov dword ptr[ebx], 5\n");
__asm("mov ebx, %0\n": :"b"(g_transition_addr));
__asm("mov dword ptr[ebx], 0xFFFFFFFC\n"); //quit session
__asm("push eax\n");
__asm("push ecx\n");
__asm("push edx\n");
__asm("call _timeGetTime@0\n");
__asm("sub eax, [_g_last_playsram]\n");
__asm("cmp eax, 10000\n"); //10sec cooldown
__asm("pop edx\n");
__asm("pop ecx\n");
__asm("ja play_soundfx_exit\n"); //skip playing sound if cooldown value not reached
__asm("pop eax\n");
__asm("jmp call_real_result\n");
__asm("play_soundfx_exit:\n");
__asm("add [_g_last_playsram], eax"); // place curr_time in g_last_playsram (cancel sub eax, [_g_last_playsram])
__asm("pop eax\n");
/* play sound fx (end session) */
__asm("push eax\n");
__asm("push ecx\n");
__asm("push edx\n");
__asm("mov eax, 0x16\n"); //"okay" sound fx
__asm("push 0\n");
__asm("call %0\n"::"D"(playsramsound_func));
__asm("add esp, 4\n");
__asm("pop edx\n");
__asm("pop ecx\n");
__asm("pop eax\n");
__asm("call_real_result:\n");
/* ----------- r2nk226 ----------- */
disp = true; // 8.9 message off later
is_resultscreen_flag = true;
hide_menu = false;
/* --------------------------------- */
real_result_loop();
}
void (*real_result_button_loop)();
void quickexit_result_button_loop()
{
if ( g_end_session || g_return_to_options )
{
//g_return_to_options is reset in quickexit_option_screen_cleanup(), g_end_session is reset in quickexit_screen_transition()
__asm("mov al, 1\n");
}
real_result_button_loop();
}
uint32_t g_timing_addr = 0;
bool g_timing_require_update = false;
void (*real_set_timing_func)();
void modded_set_timing_func()
{
if (!g_timing_require_update)
{
real_set_timing_func();
return;
}
/* timing contains offset, add to it instead of replace */
__asm("push ebx\n");
__asm("mov ebx, %0\n"::"m"(g_timing_addr));
__asm("add [ebx], eax\n");
__asm("pop ebx\n");
g_timing_require_update = false;
return;
}
volatile uint32_t g_masked_hidden = 0;
uint32_t g_show_hidden_addr = 0; /* offset from ESP at which hidden setting value is */
void (*real_show_hidden_result)();
void asm_show_hidden_result()
{
if (g_masked_hidden)
{
__asm("push edx\n");
__asm("mov edx, esp\n");
__asm("add edx, %0\n"::""(g_show_hidden_addr):);
__asm("add edx, 4\n"); /* to account for the "push edx" */
__asm("or dword ptr [edx], 0x00000001\n");
g_masked_hidden = 0;
__asm("pop edx\n");
}
__asm("call_real_hidden_result:\n");
real_show_hidden_result();
}
void (*real_stage_update)();
void hook_stage_update()
{
__asm("mov ebx, dword ptr [esi+0x14]\n");
__asm("lea ebx, [ebx+0xC]\n");
__asm("mov %0, ebx\n":"=b"(g_transition_addr): :);
__asm("push eax");
__asm("call %0"::"a"(popn22_is_normal_mode));
__asm("test al,al");
__asm("pop eax");
__asm("je skip_stage_update_pfree_check");
if ( !g_pfree_mode )
{
__asm("skip_stage_update_pfree_check:");
real_stage_update();
}
}
/* this hook is installed only when back_to_song_select is enabled and pfree is not */
void (*real_stage_increment)();
void hook_stage_increment()
{
if ( !g_return_to_song_select )
real_stage_increment();
else
g_return_to_song_select = false;
}
void (*real_check_music_idx)();
extern "C" void check_music_idx();
extern "C" int8_t check_music_idx_handler(int32_t music_idx, int32_t chart_idx, int32_t result) {
int8_t override_flag = get_chart_type_override(new_buffer_addrs[MUSIC_TABLE_IDX], music_idx & 0xffff, chart_idx & 0x0f);
LOG("music_idx: %d, result: %d, override_flag: %d\n", music_idx & 0xffff, result, override_flag);
if (override_flag != -1) {
return override_flag;
}
return (music_idx & 0xffff) > limit_table[CHART_TABLE_IDX] ? 1 : result;
}
asm(
".global _check_music_idx\n"
"_check_music_idx:\n"
" call [_real_check_music_idx]\n"
" movzx eax, al\n"
" push eax\n"
" push ebx\n"
" push esi\n"
" call _check_music_idx_handler\n"
" add esp, 12\n"
" ret\n"
);
void (*real_check_music_idx_usaneko)();
extern "C" void check_music_idx_usaneko();
extern "C" int8_t check_music_idx_handler_usaneko(int32_t result, int32_t chart_idx, int32_t music_idx) {
return check_music_idx_handler(music_idx, chart_idx, result);
}
asm(
".global _check_music_idx_usaneko\n"
"_check_music_idx_usaneko:\n"
" push edi\n"
" push ebx\n"
" cmp eax, 0x18\n"
" setl al\n"
" movzx eax, al\n"
" push eax\n"
" call _check_music_idx_handler_usaneko\n"
" add esp, 12\n"
" jmp [_real_check_music_idx_usaneko]\n"
);
char *parse_patchdb(const char *input_filename, char *base_data, membuf_t *membuf) {
property* config_xml;
if ( input_filename == NULL )
{
config_xml = load_prop_membuf(membuf);
}
else
{
const char *folder = "data_mods\\";
char *input_filepath = (char*)calloc(strlen(input_filename) + strlen(folder) + 1, sizeof(char));
sprintf(input_filepath, "%s%s", folder, input_filename);
config_xml = load_prop_file(input_filepath);
free(input_filepath);
}
char *target = (char*)calloc(64, sizeof(char));
property_node_refer(config_xml, property_search(config_xml, NULL, "/patches"), "target@", PROPERTY_TYPE_ATTR, target, 64);
READ_U32(config_xml, NULL, "/patches/limits/music", limit_music)
READ_U32(config_xml, NULL, "/patches/limits/chart", limit_chart)
READ_U32(config_xml, NULL, "/patches/limits/style", limit_style)
READ_U32(config_xml, NULL, "/patches/limits/flavor", limit_flavor)
READ_U32(config_xml, NULL, "/patches/limits/chara", limit_chara)
limit_table[MUSIC_TABLE_IDX] = limit_music;
limit_table[CHART_TABLE_IDX] = limit_chart;
limit_table[STYLE_TABLE_IDX] = limit_style;
limit_table[FLAVOR_TABLE_IDX] = limit_flavor;
limit_table[CHARA_TABLE_IDX] = limit_chara;
READ_HEX(config_xml, NULL, "/patches/buffer_base_addrs/music", buffer_addrs[MUSIC_TABLE_IDX])
buffer_addrs[MUSIC_TABLE_IDX] = buffer_addrs[MUSIC_TABLE_IDX] > 0 ? (uint64_t)base_data + (buffer_addrs[MUSIC_TABLE_IDX] - 0x10000000) : buffer_addrs[MUSIC_TABLE_IDX];
READ_HEX(config_xml, NULL, "/patches/buffer_base_addrs/chart", buffer_addrs[CHART_TABLE_IDX])
buffer_addrs[CHART_TABLE_IDX] = buffer_addrs[CHART_TABLE_IDX] > 0 ? (uint64_t)base_data + (buffer_addrs[CHART_TABLE_IDX] - 0x10000000) : buffer_addrs[CHART_TABLE_IDX];
READ_HEX(config_xml, NULL, "/patches/buffer_base_addrs/style", buffer_addrs[STYLE_TABLE_IDX])
buffer_addrs[STYLE_TABLE_IDX] = buffer_addrs[STYLE_TABLE_IDX] > 0 ? (uint64_t)base_data + (buffer_addrs[STYLE_TABLE_IDX] - 0x10000000) : buffer_addrs[STYLE_TABLE_IDX];
READ_HEX(config_xml, NULL, "/patches/buffer_base_addrs/flavor", buffer_addrs[FLAVOR_TABLE_IDX])
buffer_addrs[FLAVOR_TABLE_IDX] = buffer_addrs[FLAVOR_TABLE_IDX] > 0 ? (uint64_t)base_data + (buffer_addrs[FLAVOR_TABLE_IDX] - 0x10000000) : buffer_addrs[FLAVOR_TABLE_IDX];
READ_HEX(config_xml, NULL, "/patches/buffer_base_addrs/chara", buffer_addrs[CHARA_TABLE_IDX])
buffer_addrs[CHARA_TABLE_IDX] = buffer_addrs[CHARA_TABLE_IDX] > 0 ? (uint64_t)base_data + (buffer_addrs[CHARA_TABLE_IDX] - 0x10000000) : buffer_addrs[CHARA_TABLE_IDX];
printf("limit music: %lld\n", limit_table[MUSIC_TABLE_IDX]);
printf("limit chart: %lld\n", limit_table[CHART_TABLE_IDX]);
printf("limit style: %lld\n", limit_table[STYLE_TABLE_IDX]);
printf("limit flavor: %lld\n", limit_table[FLAVOR_TABLE_IDX]);
printf("limit chara: %lld\n", limit_table[CHARA_TABLE_IDX]);
printf("buffer music: %llx\n", buffer_addrs[MUSIC_TABLE_IDX]);
printf("buffer chart: %llx\n", buffer_addrs[CHART_TABLE_IDX]);
printf("buffer style: %llx\n", buffer_addrs[STYLE_TABLE_IDX]);
printf("buffer flavor: %llx\n", buffer_addrs[FLAVOR_TABLE_IDX]);
printf("buffer chara: %llx\n", buffer_addrs[CHARA_TABLE_IDX]);
const char *types[LIMIT_TABLE_SIZE] = {"music", "chart", "style", "flavor", "chara"};
// Read buffers_patch_addrs
for (size_t i = 0; i < LIMIT_TABLE_SIZE; i++) {
const char strbase[] = "/patches/buffers_patch_addrs";
size_t search_str_len = strlen(strbase) + 1 + strlen(types[i]) + 1;
char *search_str = (char*)calloc(search_str_len, sizeof(char));
if (!search_str) {
printf("Couldn't create buffer of size %d\n", search_str_len);
exit(1);
}
sprintf(search_str, "%s/%s", strbase, types[i]);
printf("search_str: %s\n", search_str);
property_node* prop = NULL;
if ((prop = property_search(config_xml, NULL, search_str))) {
for (; prop != NULL; prop = property_node_traversal(prop, TRAVERSE_NEXT_SEARCH_RESULT)) {
uint64_t offset = 0;
READ_HEX(config_xml, prop, "", offset)
printf("offset ptr: %llx\n", offset);
UpdateBufferEntry *buffer_offset = new UpdateBufferEntry();
buffer_offset->type = i;
buffer_offset->offset = offset;
buffer_offsets.push_back(buffer_offset);
}
}
}
// Read other_patches
for (size_t i = 0; i < LIMIT_TABLE_SIZE; i++) {
const char strbase[] = "/patches/other_patches";
size_t search_str_len = strlen(strbase) + 1 + strlen(types[i]) + 1;
char *search_str = (char*)calloc(search_str_len, sizeof(char));
if (!search_str) {
printf("Couldn't create buffer of size %d\n", search_str_len);
exit(1);
}
sprintf(search_str, "%s/%s", strbase, types[i]);
printf("search_str: %s\n", search_str);
property_node* prop = NULL;
if ((prop = property_search(config_xml, NULL, search_str))) {
for (; prop != NULL; prop = property_node_traversal(prop, TRAVERSE_NEXT_SEARCH_RESULT)) {
char methodStr[256] = {};
property_node_refer(config_xml, prop, "method@", PROPERTY_TYPE_ATTR, methodStr, sizeof(methodStr));
uint32_t method = atoi(methodStr);
char expectedStr[256] = {};
property_node_refer(config_xml, prop, "expected@", PROPERTY_TYPE_ATTR, expectedStr, sizeof(expectedStr));
uint64_t expected = strtol((const char*)expectedStr, NULL, 16);
char sizeStr[256] = {};
property_node_refer(config_xml, prop, "size@", PROPERTY_TYPE_ATTR, sizeStr, sizeof(sizeStr));
uint64_t size = strtol((const char*)sizeStr, NULL, 16);
if (size == 0) {
// I can't think of any patches that require a different size than 4 bytes
size = 4;
}
uint64_t offset = 0;
READ_HEX(config_xml, prop, "", offset)
printf("offset ptr: %llx, method = %d, expected = %llx, size = %lld\n", offset, method, expected, size);
UpdateOtherEntry *other_offset = new UpdateOtherEntry();
other_offset->type = i;
other_offset->method = method;
other_offset->offset = offset;
other_offset->expectedValue = expected;
other_offset->size = size;
other_offsets.push_back(other_offset);
}
}
}
// Read hook_addrs
{
property_node* prop = NULL;
if ((prop = property_search(config_xml, NULL, "/patches/hook_addrs/offset"))) {
for (; prop != NULL; prop = property_node_traversal(prop, TRAVERSE_NEXT_SEARCH_RESULT)) {
char methodStr[256] = {};
property_node_refer(config_xml, prop, "method@", PROPERTY_TYPE_ATTR, methodStr, sizeof(methodStr));
uint32_t method = atoi(methodStr);
uint64_t offset = 0;
READ_HEX(config_xml, prop, "", offset)
printf("offset ptr: %llx\n", offset);
HookEntry *hook_offset = new HookEntry();
hook_offset->offset = offset;
hook_offset->method = method;
hook_offsets.push_back(hook_offset);
}
}
}
return target;
}
static bool patch_purelong()
{
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
{
int64_t pattern_offset = _search(data, dllSize, "\x80\x1A\x06\x00\x83\xFA\x08\x77\x08", 9, 0);
if (pattern_offset == -1) {
LOG("popnhax: Couldn't find score increment function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 24;
uint8_t *patch_str = (uint8_t *) patch_addr;
DWORD old_prot;
VirtualProtect((LPVOID)patch_str, 20, PAGE_EXECUTE_READWRITE, &old_prot);
/* replace cdq/idiv by xor/div in score increment computation to avoid score overflow */
for (int i=12; i>=0; i--)
{
patch_str[i+1] = patch_str[i];
}
patch_str[0] = 0x33;
patch_str[1] = 0xD2;
patch_str[3] = 0x34;
VirtualProtect((LPVOID)patch_str, 20, old_prot, &old_prot);
}
return true;
}
void (*real_normal0)();
void hook_normal0()
{
// getChartDifficulty returns 0xFFFFFFFF when there's no chart,
// but the game assumes there's always a normal chart so there's no check in this case
// and it returns 0... let's fix this
__asm("cmp ebx, 1\n"); //chart id
__asm("jne process_normal0\n");
__asm("cmp eax, 0\n"); //difficulty
__asm("jne process_normal0\n");
__asm("or eax, 0xFFFFFFFF\n");
__asm("process_normal0:\n");
real_normal0();
}
static bool patch_normal0()
{
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
{
int64_t pattern_offset = _search(data, dllSize, "\x83\xC4\x08\x8B\xF8\x89\x7C\x24\x3C", 9, 0);
if (pattern_offset == -1) {
pattern_offset = _search(data, dllSize, "\x83\xC4\x08\x8B\xF8\x89\x7C\x24\x44", 9, 0);
if (pattern_offset == -1) {
LOG("popnhax: normal0: Couldn't find song list display function\n");
return false;
}
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)hook_normal0,
(void **)&real_normal0);
}
return true;
}
static bool get_music_limit(uint32_t* limit) {
// avoid doing the search multiple times
static uint32_t music_limit = 0;
if ( music_limit != 0 )
{
*limit = music_limit;
return true;
}
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
PIMAGE_NT_HEADERS headers = (PIMAGE_NT_HEADERS)((int64_t)data + ((PIMAGE_DOS_HEADER)data)->e_lfanew);
DWORD_PTR reloc_delta = (DWORD_PTR)((int64_t)data - headers->OptionalHeader.ImageBase);
{
int64_t string_loc = _search(data, dllSize, "Illegal music no %d", 19, 0);
if (string_loc == -1) {
LOG("popnhax: patch_db: could not retrieve music limit error string\n");
return false;
}
string_loc += reloc_delta; //reloc delta just in case
string_loc += 0x10000000; //entrypoint
char *as_hex = (char *) &string_loc;
int64_t pattern_offset = _search(data, dllSize, as_hex, 4, 0);
if (pattern_offset == -1) {
LOG("popnhax: could not retrieve music limit test function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset - 0x1F;
*limit = *(uint32_t*)patch_addr;
music_limit = *limit;
}
return true;
}
char *get_datecode_from_patches() {
SearchFile s;
char datecode[11] = {0};
uint32_t music_limit = 0;
if ( !get_music_limit(&music_limit) )
{
LOG("WARNING: could not retrieve music limit for datecode_auto\n");
return NULL;
}
s.search("data_mods", "xml", false);
auto result = s.getResult();
for (uint16_t i=0; i<result.size(); i++) {
uint32_t found_limit = 0;
property *patch_xml = load_prop_file(result[i].c_str());
READ_U32_OPT(patch_xml, property_search(patch_xml, NULL, "/patches/limits"), "music", found_limit);
int res = property_node_refer(patch_xml, property_search(patch_xml, NULL, "/patches"), "target@",
PROPERTY_TYPE_ATTR, datecode, 11);
free(patch_xml);
if ( found_limit == music_limit )
{
if ( res != 11 )
{
LOG("WARNING: %s did not contain a valid target, fallback to filename\n", result[i].c_str()+10);
memcpy(datecode, result[i].c_str()+10+8, 10);
}
return strdup(datecode);
}
}
LOG("WARNING: datecode_auto: could not find matching patch file (patch file can still be generated)\n");
return NULL;
}
static bool patch_datecode(char *datecode) {
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
if ( strcmp(datecode, "auto") == 0 )
{
if ( !config.patch_db )
{
return true;
}
LOG("popnhax: find datecode based on patches_.xml file\n");
g_datecode_override = get_datecode_from_patches();
if ( g_datecode_override == NULL )
return false;
}
else
g_datecode_override = strdup(datecode);
{
int64_t pattern_offset = _search(data, dllSize, "\x8D\x44\x24\x10\x88\x4C\x24\x10\x88\x5C\x24\x11\x8D\x50\x01", 15, 0);
if (pattern_offset != -1) {
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x08;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)asm_patch_datecode,
(void **)&real_asm_patch_datecode);
LOG("popnhax: datecode set to %s",g_datecode_override);
} else {
LOG("popnhax: Couldn't patch datecode\n");
return false;
}
}
if (!config.network_datecode)
{
LOG("\n");
return true;
}
/* network_datecode is on: also patch libavs so that forced datecode shows in network packets */
DWORD avsdllSize = 0;
char *avsdata = getDllData("libavs-win32.dll", &avsdllSize);
{
int64_t pattern_offset = _search(avsdata, avsdllSize, "\x57\x56\x89\x34\x24\x8B\xF2\x8B\xD0\x0F\xB6\x46\x2E", 13, 0);
if (pattern_offset != -1) {
uint64_t patch_addr = (int64_t)avsdata + pattern_offset;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)asm_patch_datecode_libavs,
(void **)&real_asm_patch_datecode_libavs);
LOG(" (including network)\n");
} else {
LOG(" (WARNING: failed to apply to network)\n");
return false;
}
}
return true;
}
static bool patch_database() {
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
char *target;
if ( strcmp(config.force_patch_xml, "") != 0 )
{
LOG("popnhax: patch_db: force patch file %s", config.force_patch_xml);
target = parse_patchdb(config.force_patch_xml, data, NULL);
}
else
{
const char *filename = NULL;
SearchFile s;
uint8_t *datecode = NULL;
bool found = false;
uint32_t music_limit = 0;
if ( !config.ignore_music_limit )
{
if ( !get_music_limit(&music_limit) ) {
LOG("WARNING: could not retrieve music limit\n");
} else {
LOG("popnhax: patch_db: music limit : %d\n", music_limit);
}
} else {
LOG("popnhax: patch_db: ignore music limit\n");
}
if ( g_datecode_override != NULL )
{
LOG("popnhax: patch_db: auto detect/generate patch file with datecode override\n");
datecode = (uint8_t*) strdup(g_datecode_override);
}
else
{
LOG("popnhax: patch_db: auto detect/generate patch file with datecode from ea3-config\n");
property *config_xml = load_prop_file("prop/ea3-config.xml");
READ_STR_OPT(config_xml, property_search(config_xml, NULL, "/ea3/soft"), "ext", datecode)
free(config_xml);
}
if (datecode == NULL) {
LOG("popnhax: patch_db: failed to retrieve datecode. You can either rename popn22.dll to popn22_<datecode>.dll, fix ea3-config.xml, use force_datecode or use force_patch_xml.\n");
exit(1);
}
LOG("popnhax: patch_db: datecode : %s\n", datecode);
LOG("popnhax: patch_db: XML patch files search...\n");
s.search("data_mods", "xml", false);
auto result = s.getResult();
LOG("popnhax: patch_db: found %d xml files in data_mods\n",result.size());
for (uint16_t i=0; i<result.size(); i++) {
filename = result[i].c_str()+10; // strip "data_mods\" since parsedb will prepend it...
LOG("%d : %s\n",i,filename);
bool datecode_match = (strstr(result[i].c_str(), (const char *)datecode) != NULL);
bool limit_match = false;
if ( music_limit != 0 )
{
uint32_t found_limit = 0;
LOG(" check music limit... ");
property *patch_xml = load_prop_file(result[i].c_str());
READ_U32_OPT(patch_xml, property_search(patch_xml, NULL, "/patches/limits"), "music", found_limit)
free(patch_xml);
LOG("%d\n",found_limit);
if ( found_limit == music_limit )
{
limit_match = true;
}
}
if ( limit_match )
{
LOG("popnhax: patch_db: found matching music limit, end search\n");
if ( !datecode_match )
{
LOG("WARNING: datecode did NOT match, please update your %s\n", ( g_datecode_override != NULL ) ? "force_datecode value" : "ea3-config.xml");
}
found = true;
break;
}
if ( datecode_match )
{
if ( music_limit == 0 )
{
found = true;
LOG("popnhax: patch_db: found matching datecode (no music limit check), end search\n");
break;
}
LOG("WARNING: found matching datecode but music limit doesn't check out, continue search\n");
}
}
if (!found) {
LOG("popnhax: patch_db: no matching patch file found, generating our own (datecode %s)\n", datecode);
membuf_t *membuf = membuf_new(30000);
if (membuf == NULL)
{
LOG("popnhax: patch_db: Failed to allocate membuf\n");
exit(1);
}
make_omnimix_patch(g_game_dll_fn, membuf, (char*)datecode);
membuf_rewind(membuf);
if ( config.patch_xml_dump )
{
char out_filename[128];
sprintf(out_filename, "data_mods\\patches_%s.xml", (char*)datecode);
LOG("popnhax: patch_db: saving generated patches to %s\n", out_filename);
membuf_tofile(membuf, out_filename);
}
target = parse_patchdb(NULL, data, membuf);
membuf_free(membuf);
} else {
LOG("popnhax: patch_db: using %s\n",filename);
target = parse_patchdb(filename, data, NULL);
}
}
if (config.disable_redirection) {
config.disable_expansions = true;
}
musichax_core_init(&config,
target,
data,
limit_table[MUSIC_TABLE_IDX],
&new_limit_table[MUSIC_TABLE_IDX],
(char*)buffer_addrs[MUSIC_TABLE_IDX],
&new_buffer_addrs[MUSIC_TABLE_IDX],
limit_table[CHART_TABLE_IDX],
&new_limit_table[CHART_TABLE_IDX],
(char*)buffer_addrs[CHART_TABLE_IDX],
&new_buffer_addrs[CHART_TABLE_IDX],
limit_table[STYLE_TABLE_IDX],
&new_limit_table[STYLE_TABLE_IDX],
(char*)buffer_addrs[STYLE_TABLE_IDX],
&new_buffer_addrs[STYLE_TABLE_IDX],
limit_table[FLAVOR_TABLE_IDX],
&new_limit_table[FLAVOR_TABLE_IDX],
(char*)buffer_addrs[FLAVOR_TABLE_IDX],
&new_buffer_addrs[FLAVOR_TABLE_IDX],
limit_table[CHARA_TABLE_IDX],
&new_limit_table[CHARA_TABLE_IDX],
(char*)buffer_addrs[CHARA_TABLE_IDX],
&new_buffer_addrs[CHARA_TABLE_IDX]
);
limit_table[STYLE_TABLE_IDX] = new_limit_table[STYLE_TABLE_IDX];
patch_purelong();
patch_normal0();
{
int64_t pattern_offset = _search(data, dllSize, "\x8D\x44\x24\x10\x88\x4C\x24\x10\x88\x5C\x24\x11\x8D\x50\x01", 15, 0);
if (pattern_offset != -1) {
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)omnimix_patch_jbx,
(void **)&real_omnimix_patch_jbx);
LOG("popnhax: Patched X rev for omnimix\n");
} else {
LOG("popnhax: Couldn't find rev patch\n");
}
}
if (config.disable_redirection) {
LOG("Redirection-related code is disabled, buffer address, buffer size and related patches will not be applied");
printf("Redirection-related code is disabled, buffer address, buffer size and related patches will not be applied");
return true;
}
// Patch buffers
for (size_t i = 0; i < buffer_offsets.size(); i++) {
if (buffer_offsets[i]->offset == 0 || buffer_addrs[buffer_offsets[i]->type] == 0) {
continue;
}
// buffer_base_offsets is required because it will point to the beginning of all of the buffers to calculate offsets
uint64_t patch_addr = (int64_t)data + (buffer_offsets[i]->offset - 0x10000000);
uint64_t cur_addr = *(int32_t*)patch_addr;
uint64_t mem_addr = (uint64_t)new_buffer_addrs[buffer_offsets[i]->type] + (cur_addr - buffer_addrs[buffer_offsets[i]->type]);
printf("Patching %llx -> %llx @ %llx (%llx - %llx = %llx)\n", cur_addr, mem_addr, buffer_offsets[i]->offset, cur_addr, buffer_addrs[buffer_offsets[i]->type], cur_addr - buffer_addrs[buffer_offsets[i]->type]);
patch_memory(patch_addr, (char*)&mem_addr, 4);
}
LOG("popnhax: patched memory db locations\n");
if (config.disable_expansions) {
printf("Expansion-related code is disabled, buffer size and related patches will not be applied");
LOG("Expansion-related code is disabled, buffer size and related patches will not be applied");
return true;
}
for (size_t i = 0; i < other_offsets.size(); i++) {
// Get current limit so it can be used for later calculations
uint32_t cur_limit = limit_table[other_offsets[i]->type];
uint32_t limit_diff = cur_limit < new_limit_table[other_offsets[i]->type]
? new_limit_table[other_offsets[i]->type] - cur_limit
: 0;
if (limit_diff != 0)
{
uint64_t cur_value = 0;
if (other_offsets[i]->size == 1) {
cur_value = *(uint8_t*)(data + (other_offsets[i]->offset - 0x10000000));
} else if (other_offsets[i]->size == 2) {
cur_value = *(uint16_t*)(data + (other_offsets[i]->offset - 0x10000000));
} else if (other_offsets[i]->size == 4) {
cur_value = *(uint32_t*)(data + (other_offsets[i]->offset - 0x10000000));
} else if (other_offsets[i]->size == 8) {
cur_value = *(uint64_t*)(data + (other_offsets[i]->offset - 0x10000000));
}
if (other_offsets[i]->method != 12 && cur_value != other_offsets[i]->expectedValue) {
printf("ERROR! Expected %llx, found %llx @ %llx!\n", other_offsets[i]->expectedValue, cur_value, other_offsets[i]->offset);
LOG("ERROR! Expected %llx, found %llx @ %llx!\n", other_offsets[i]->expectedValue, cur_value, other_offsets[i]->offset);
exit(1);
}
uint64_t value = cur_value;
uint32_t patch_size = 4;
switch (other_offsets[i]->method) {
// Add limit_diff to value
case 0:
value = cur_value + limit_diff;
break;
// Add limit_diff * 4 to value
case 1:
value = cur_value + (limit_diff * 4);
break;
// Add limit diff * 6 * 4 (number of charts * 4?)
case 2:
value = cur_value + ((limit_diff * 6) * 4);
break;
// Add limit_diff * 6
case 3:
value = cur_value + (limit_diff * 6);
break;
// Add limit_diff * 0x90
case 4:
value = cur_value + (limit_diff * 0x90);
break;
// Add limit_diff * 0x48
case 5:
value = cur_value + (limit_diff * 0x48);
break;
// Add limit_diff * 0x120
case 6:
value = cur_value + (limit_diff * 0x120);
break;
// Add limit_diff * 0x440
case 7:
value = cur_value + (limit_diff * 0x440);
break;
// Add limit_diff * 0x0c
case 8:
value = cur_value + (limit_diff * 0x0c);
break;
// Add limit_diff * 0x3d0
case 9:
value = cur_value + (limit_diff * 0x3d0);
break;
// Add (limit_diff * 0x3d0) + (limit_diff * 0x9c)
case 10:
value = cur_value + (limit_diff * 0x3d0) + (limit_diff * 0x9c);
break;
// Add (limit_diff * 0x3d0) + (limit_diff * 0x9c) + (limit_diff * 0x2a0)
case 11:
value = cur_value + (limit_diff * 0x3d0) + (limit_diff * 0x9c) + (limit_diff * 0x2a0);
break;
// NOP
case 12:
value = 0x9090909090909090;
patch_size = other_offsets[i]->size;
break;
default:
printf("Unknown command: %lld\n", other_offsets[i]->method);
break;
}
if (value != cur_value) {
uint64_t patch_addr = (int64_t)data + (other_offsets[i]->offset - 0x10000000);
patch_memory(patch_addr, (char*)&value, patch_size);
printf("Patched %llx: %llx -> %llx\n", other_offsets[i]->offset, cur_value, value);
}
}
}
HMODULE _moduleBase = GetModuleHandle(g_game_dll_fn);
for (size_t i = 0; i < hook_offsets.size(); i++) {
switch (hook_offsets[i]->method) {
case 0: {
// Peace hook
printf("Hooking %llx %p\n", hook_offsets[i]->offset, _moduleBase);
_MH_CreateHook((void*)((uint8_t*)_moduleBase + (hook_offsets[i]->offset - 0x10000000)), (void *)&check_music_idx, (void **)&real_check_music_idx);
break;
}
case 1: {
// Usaneko hook
printf("Hooking %llx %p\n", hook_offsets[i]->offset, _moduleBase);
uint8_t nops[] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };
patch_memory((uint64_t)((uint8_t*)_moduleBase + (hook_offsets[i]->offset - 0x10000000) - 6), (char *)&nops, 6);
_MH_CreateHook((void*)((uint8_t*)_moduleBase + (hook_offsets[i]->offset - 0x10000000)), (void *)&check_music_idx_usaneko, (void **)&real_check_music_idx_usaneko);
break;
}
default:
printf("Unknown hook command: %lld\n", hook_offsets[i]->method);
break;
}
}
LOG("popnhax: patched limit-related code\n");
return true;
}
static bool patch_audio_source_fix() {
if (!find_and_patch_hex(g_game_dll_fn, "\x85\xC0\x75\x96\x8D\x70\x7F\xE8\xF8\x2B\x00\x00", 12, 0, "\x90\x90\x90\x90", 4))
{
return false;
}
LOG("popnhax: audio source fixed\n");
return true;
}
static bool patch_unset_volume() {
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
int64_t first_loc = _search(data, dllSize, "\x04\x00\x81\xC4\x00\x01\x00\x00\xC3\xCC", 10, 0);
if (first_loc == -1) {
return false;
}
int64_t pattern_offset = _search(data, 0x10, "\x83", 1, first_loc);
if (pattern_offset == -1) {
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
patch_memory(patch_addr, (char *)"\xC3", 1);
LOG("popnhax: windows volume untouched\n");
return true;
}
static bool patch_event_mode() {
if (!find_and_patch_hex(g_game_dll_fn, "\x8B\x00\xC3\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC"
"\xCC\xCC\xC7", 17, 0, "\x31\xC0\x40\xC3", 4))
{
return false;
}
LOG("popnhax: event mode forced\n");
return true;
}
static bool patch_remove_timer() {
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
int64_t first_loc = _search(data, dllSize, "\x8B\xAC\x24\x68\x01", 5, 0);
if (first_loc == -1) {
return false;
}
int64_t pattern_offset = _search(data, 0x15, "\x0F", 1, first_loc);
if (pattern_offset == -1) {
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
patch_memory(patch_addr, (char *)"\x90\xE9", 2);
LOG("popnhax: timer removed\n");
return true;
}
static bool patch_freeze_timer() {
if (!find_and_patch_hex(g_game_dll_fn, "\xC7\x45\x38\x09\x00\x00\x00", 7, 0, "\x90\x90\x90\x90\x90\x90\x90", 7))
{
return false;
}
LOG("popnhax: timer frozen at 10 seconds remaining\n");
return true;
}
static bool patch_skip_tutorials() {
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
{
int64_t first_loc = _search(data, dllSize, "\xFD\xFF\x5E\xC2\x04\x00\xE8", 7, 0);
if (first_loc == -1) {
return false;
}
int64_t pattern_offset = _search(data, 0x10, "\x84\xC0\x74", 3, first_loc);
if (pattern_offset == -1) {
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 2;
patch_memory(patch_addr, (char *)"\xEB", 1);
}
{
int64_t pattern_offset = _search(data, dllSize, "\x66\x85\xC0\x75\x5E\x6A", 6, 0);
if (pattern_offset == -1) {
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
patch_memory(patch_addr, (char *)"\x66\x85\xC0\xEB\x5E\x6A", 6);
}
{
int64_t pattern_offset = _search(data, dllSize, "\x00\x5F\x5E\x66\x83\xF8\x01\x75", 8, 0);
if (pattern_offset == -1) {
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
patch_memory(patch_addr, (char *)"\x00\x5F\x5E\x66\x83\xF8\x01\xEB", 8);
}
LOG("popnhax: menu and long note tutorials skipped\n");
return true;
}
bool force_unlock_deco_parts() {
if (!find_and_patch_hex(g_game_dll_fn, "\x83\xC4\x04\x83\x38\x00\x75\x22", 8, 6, "\x90\x90", 2))
{
LOG("popnhax: no deco parts to unlock\n");
return false;
}
LOG("popnhax: unlocked deco parts\n");
return true;
}
bool force_unlock_songs() {
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
int music_unlocks = 0, chart_unlocks = 0;
{
// 0xac here is the size of music_entry. May change in the future
int64_t pattern_offset = _search(data, dllSize, "\x75\x0E\x98\x69\xC0\xAC\x00\x00\x00\x8B\x80", 11, 0);
if (pattern_offset == -1) {
LOG("popnhax: couldn't unlock songs and charts\n");
return false;
}
uint32_t buffer_offset = *(uint32_t*)((int64_t)data + pattern_offset + 11);
buffer_offset -= 0x1c; // The difference between music_entry.mask and music_entry.fw_genre_ptr to put it at the beginning of the entry
music_entry *entry = (music_entry*)buffer_offset;
for (int32_t i = 0; ; i++) {
// Detect end of table
// Kind of iffy but I think it should work
if (entry->charts[6] != 0 && entry->hold_flags[7] != 0) {
// These should *probably* always be 0 but won't be after the table ends
break;
}
if ((entry->mask & 0x00020000) != 0) {
LOG("[%04d][JF] Unlocking %s\n", i, entry->title_ptr);
}
if ((entry->mask & 0x08000000) != 0) {
LOG("[%04d] Unlocking %s\n", i, entry->title_ptr);
music_unlocks++;
}
if ((entry->mask & 0x00000080) != 0) {
LOG("[%04d] Unlocking charts for %s\n", i, entry->title_ptr);
chart_unlocks++;
}
if ((entry->mask & 0x08020080) != 0) {
uint32_t new_mask = entry->mask & ~0x08020080;
patch_memory((uint64_t)&entry->mask, (char *)&new_mask, 4);
}
entry++; // Move to the next music entry
}
}
LOG("popnhax: unlocked %d songs and %d charts\n", music_unlocks, chart_unlocks);
return true;
}
bool force_unlock_charas() {
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
int chara_unlocks = 0;
{
// 0x4c here is the size of character_entry. May change in the future
int64_t pattern_offset = _search(data, dllSize, "\x98\x6B\xC0\x4C\x8B\x80", 6, 0);
if (pattern_offset == -1) {
LOG("popnhax: couldn't unlock characters\n");
return false;
}
uint32_t buffer_offset = *(uint32_t*)((int64_t)data + pattern_offset + 6);
buffer_offset -= 0x48; // The difference between character_entry.game_version and character_entry.chara_id_ptr to put it at the beginning of the entry
character_entry *entry = (character_entry*)buffer_offset;
for (int32_t i = 0; ; i++) {
// Detect end of table
// Kind of iffy but I think it should work
if (entry->_pad1[0] != 0 || entry->_pad2[0] != 0 || entry->_pad2[1] != 0 || entry->_pad2[2] != 0) {
// These should *probably* always be 0 but won't be after the table ends
break;
}
uint32_t new_flags = entry->flags & ~3;
if (new_flags != entry->flags && entry->disp_name_ptr != NULL && strlen((char*)entry->disp_name_ptr) > 0) {
LOG("Unlocking [%04d] %s... %08x -> %08x\n", i, entry->disp_name_ptr, entry->flags, new_flags);
patch_memory((uint64_t)&entry->flags, (char *)&new_flags, sizeof(uint32_t));
if ((entry->flavor_idx == 0 || entry->flavor_idx == -1)) {
int flavor_idx = 1;
patch_memory((uint64_t)&entry->flavor_idx, (char *)&flavor_idx, sizeof(uint32_t));
LOG("Setting default flavor for chara id %d\n", i);
}
chara_unlocks++;
}
entry++; // Move to the next character entry
}
}
LOG("popnhax: unlocked %d characters\n", chara_unlocks);
return true;
}
static bool patch_unlocks_offline() {
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
{
int64_t pattern_offset = _search(data, dllSize-0xE0000, "\xB8\x49\x06\x00\x00\x66\x3B", 7, 0xE0000);
if (pattern_offset == -1) {
LOG("Couldn't find first song unlock\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset - 2;
patch_memory(patch_addr, (char *)"\x90\x90", 2);
}
{
if (!find_and_patch_hex(g_game_dll_fn, "\xA9\x06\x00\x00\x68\x74", 6, 5, "\xEB", 1))
{
LOG("Couldn't find second song unlock\n");
return false;
}
}
LOG("popnhax: songs unlocked for offline\n");
{
if (!find_and_patch_hex(g_game_dll_fn, "\xA9\x10\x01\x00\x00\x74", 6, 5, "\xEB", 1) /* unilab */
&& !find_and_patch_hex(g_game_dll_fn, "\xA9\x50\x01\x00\x00\x74", 6, 5, "\xEB", 1))
{
LOG("Couldn't find character unlock\n");
return false;
}
LOG("popnhax: characters unlocked for offline\n");
}
return true;
}
/* helper function to retrieve numpad status address */
static bool get_addr_icca(uint32_t *res)
{
static uint32_t addr = 0;
if (addr != 0)
{
*res = addr;
return true;
}
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
int64_t pattern_offset = _search(data, dllSize, "\xE8\x4B\x14\x00\x00\x84\xC0\x74\x03\x33\xC0\xC3\x8B\x0D", 14, 0);
if (pattern_offset == -1) {
return false;
}
addr = *(uint32_t *) ((int64_t)data + pattern_offset + 14);
#if DEBUG == 1
LOG("ICCA MEMORYZONE %x\n", addr);
#endif
*res = addr;
return true;
}
/* helper function to retrieve timing offset global var address */
static bool get_addr_timing_offset(uint32_t *res)
{
static uint32_t addr = 0;
if (addr != 0)
{
*res = addr;
return true;
}
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
int64_t pattern_offset = _search(data, dllSize, "\xB8\xB4\xFF\xFF\xFF", 5, 0);
if (pattern_offset == -1) {
return false;
}
uint32_t offset_delta = *(uint32_t *) ((int64_t)data + pattern_offset + 6);
addr = *(uint32_t *) (((int64_t)data + pattern_offset + 10) + offset_delta + 1);
#if DEBUG == 1
LOG("OFFSET MEMORYZONE %x\n", addr);
#endif
*res = addr;
return true;
}
/* helper function to retrieve beam brightness address */
static bool get_addr_beam_brightness(uint32_t *res)
{
static uint32_t addr = 0;
if (addr != 0)
{
*res = addr;
return true;
}
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
int64_t pattern_offset = _search(data, dllSize, "\xB8\x64\x00\x00\x00\xD9", 6, 0);
if (pattern_offset == -1) {
return false;
}
addr = (uint32_t) ((int64_t)data + pattern_offset + 1);
#if DEBUG == 1
LOG("BEAM BRIGHTNESS MEMORYZONE %x\n", addr);
#endif
*res = addr;
return true;
}
/* helper function to retrieve SD timing address */
static bool get_addr_sd_timing(uint32_t *res)
{
static uint32_t addr = 0;
if (addr != 0)
{
*res = addr;
return true;
}
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
int64_t pattern_offset = _search(data, dllSize, "\xB8\xC4\xFF\xFF\xFF", 5, 0);
if (pattern_offset == -1) {
return false;
}
addr = (uint32_t) ((int64_t)data + pattern_offset + 1);
#if DEBUG == 1
LOG("SD TIMING MEMORYZONE %x\n", addr);
#endif
*res = addr;
return true;
}
/* helper function to retrieve HD timing address */
static bool get_addr_hd_timing(uint32_t *res)
{
static uint32_t addr = 0;
if (addr != 0)
{
*res = addr;
return true;
}
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
int64_t pattern_offset = _search(data, dllSize, "\xB8\xB4\xFF\xFF\xFF", 5, 0);
if (pattern_offset == -1) {
return false;
}
addr = (uint32_t) ((int64_t)data + pattern_offset + 1);
#if DEBUG == 1
LOG("HD TIMING MEMORYZONE %x\n", addr);
#endif
*res = addr;
return true;
}
void (*real_commit_options_new)();
void hidden_is_offset_commit_options_new()
{
/* game goes through this hook twice per commit, so we need to be careful */
__asm("add eax, 0x2D\n"); //hidden value offset
__asm("push ebx\n");
__asm("movzx ebx, byte ptr [eax]\n");
__asm("or [_g_masked_hidden], ebx\n"); /* save to restore display when using result_screen_show_offset patch */
__asm("pop ebx\n");
__asm("cmp byte ptr [eax], 1\n");
__asm("jne call_real_commit_new\n");
/* disable hidden ingame */
__asm("mov byte ptr [eax], 0\n");
/* flag timing for update */
__asm("mov dword ptr [_g_timing_require_update], 1\n");
/* write into timing offset */
__asm("push ebx\n");
__asm("push eax\n");
__asm("movsx eax, word ptr [eax+1]\n");
__asm("neg eax\n");
__asm("mov ebx, %0\n"::"m"(g_timing_addr));
__asm("mov [ebx], eax\n");
__asm("pop eax\n");
__asm("pop ebx\n");
/* quit */
__asm("call_real_commit_new:\n");
__asm("sub eax, 0x2D\n");
real_commit_options_new();
}
void (*real_commit_options)();
void hidden_is_offset_commit_options()
{
__asm("push eax\n");
/* check if hidden active */
__asm("xor eax, eax\n");
__asm("mov eax, [esi]\n");
__asm("shr eax, 0x18\n");
__asm("or %0, eax\n":"=m"(g_masked_hidden):); /* save to restore display when using result_screen_show_offset patch */
__asm("cmp eax, 1\n");
__asm("jne call_real_commit\n");
/* disable hidden ingame */
__asm("and ecx, 0x00FFFFFF\n"); // -kaimei
__asm("and edx, 0x00FFFFFF\n"); // unilab
/* flag timing for update */
__asm("mov %0, 1\n":"=m"(g_timing_require_update):);
//g_timing_require_update = true;
/* write into timing offset */
__asm("push ebx\n");
__asm("movsx eax, word ptr [esi+4]\n");
__asm("neg eax\n");
__asm("mov ebx, %0\n"::"m"(g_timing_addr));
__asm("mov [ebx], eax\n");
__asm("pop ebx\n");
/* quit */
__asm("call_real_commit:\n");
__asm("pop eax\n");
real_commit_options();
}
static bool patch_hidden_is_offset()
{
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
if (!get_addr_timing_offset(&g_timing_addr))
{
LOG("popnhax: hidden is offset: cannot find timing offset address\n");
return false;
}
/* patch option commit to store hidden value directly as offset */
{
uint8_t shift;
int64_t pattern_offset;
uint64_t patch_addr;
if (config.game_version == 27)
{
shift = 6;
pattern_offset = _search(data, dllSize, "\x03\xC7\x8D\x44\x01\x2A\x89\x10", 8, 0);
}
else if (config.game_version < 27)
{
pattern_offset = _search(data, dllSize, "\x0F\xB6\xC3\x03\xCF\x8D", 6, 0);
shift = 14;
}
else
{
shift = 25;
pattern_offset = _search(data, dllSize, "\x6B\xC9\x1A\x03\xC6\x8B\x74\x24\x10", 9, 0);
patch_addr = (int64_t)data + pattern_offset + shift;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hidden_is_offset_commit_options_new,
(void **)&real_commit_options_new);
}
if (pattern_offset == -1) {
LOG("popnhax: hidden is offset: cannot find address\n");
return false;
}
patch_addr = (int64_t)data + pattern_offset + shift;
if ( config.game_version <= 27 )
{
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hidden_is_offset_commit_options,
(void **)&real_commit_options);
}
}
/* turn "set offset" into an elaborate "sometimes add to offset" to use our hidden+ value as adjust */
{
char set_offset_fun[6] = "\xA3\x00\x00\x00\x00";
uint32_t *cast_code = (uint32_t*) &set_offset_fun[1];
*cast_code = g_timing_addr;
int64_t pattern_offset = _search(data, dllSize, set_offset_fun, 5, 0);
if (pattern_offset == -1) {
LOG("popnhax: hidden is offset: cannot find offset update function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)modded_set_timing_func,
(void **)&real_set_timing_func);
}
LOG("popnhax: hidden is offset: hidden is now an offset adjust\n");
return true;
}
static bool patch_show_hidden_adjust_result_screen() {
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
int64_t first_loc = _search(data, dllSize, "\x6A\x00\x0F\xBE\xCB", 5, 0);
if (first_loc == -1)
return false;
int64_t pattern_offset = _search(data, 0x200, "\x80\xBC\x24", 3, first_loc);
if (pattern_offset == -1) {
return false;
}
g_show_hidden_addr = *((uint32_t *)((int64_t)data + pattern_offset + 0x03));
uint64_t hook_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)(hook_addr), (LPVOID)asm_show_hidden_result,
(void **)&real_show_hidden_result);
LOG("popnhax: show hidden/adjust value on result screen\n");
return true;
}
static bool force_show_fast_slow() {
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
int64_t first_loc = _search(data, dllSize, "\x6A\x00\x0F\xBE\xCB", 5, 0);
if (first_loc == -1) {
return false;
}
{
int64_t pattern_offset = _search(data, 0x50, "\x0F\x85", 2, first_loc);
if (pattern_offset == -1) {
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
patch_memory(patch_addr, (char *)"\x90\x90\x90\x90\x90\x90", 6);
}
LOG("popnhax: always show fast/slow on result screen\n");
return true;
}
void (*real_show_detail_result)();
void hook_show_detail_result(){
static uint32_t last_call = 0;
__asm("push eax\n");
__asm("push edx\n");
uint32_t curr_time = timeGetTime(); //will clobber eax
if ( curr_time - last_call > 10000 ) //will clobber edx
{
last_call = curr_time;
__asm("pop edx\n");
__asm("pop eax\n");
//force press yellow button
__asm("mov al, 1\n");
}
else
{
last_call = curr_time;
__asm("pop edx\n");
__asm("pop eax\n");
}
real_show_detail_result();
}
static bool force_show_details_result() {
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
int64_t first_loc = _search(data, dllSize, "\x8B\x45\x48\x8B\x58\x0C\x6A\x09\x68\x80\x00\x00\x00", 13, 0);
if (first_loc == -1) {
LOG("popnhax: show details: cannot find result screen button check (1)\n");
return false;
}
{
int64_t pattern_offset = _search(data, 0x50, "\x84\xC0", 2, first_loc);
if (pattern_offset == -1) {
LOG("popnhax: show details: cannot find result screen button check (2)\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)hook_show_detail_result,
(void **)&real_show_detail_result);
}
LOG("popnhax: force show details on result screen\n");
return true;
}
uint8_t g_pfree_song_offset = 0x54;
uint16_t g_pfree_song_offset_2 = 0x558;
void (*popn22_get_powerpoints)();
void (*popn22_get_chart_level)();
/* POWER POINTS LIST FIX */
uint8_t g_pplist_idx = 0; // also serves as elem count
int32_t g_pplist[20] = {0}; // 20 elements for power_point_list (always ordered)
int32_t g_power_point_value = -1; // latest value (hook uses update_pplist() to add to g_pplist array)
int32_t *g_real_pplist; // list that the game retrieves from server
uint32_t *allocated_pplist_copy; // pointer to the location where the game's pp_list modified copy resides
void pplist_reset()
{
for (int i=0; i<20; i++)
g_pplist[i] = -1;
g_pplist_idx = 0;
}
/* add new value (stored in g_power_point_value) to g_pp_list */
void pplist_update(){
if ( g_power_point_value == -1 )
return;
if ( g_pplist_idx == 20 )
{
for (int i = 0; i < 19; i++)
{
g_pplist[i] = g_pplist[i+1];
}
g_pplist_idx = 19;
}
g_pplist[g_pplist_idx++] = g_power_point_value;
g_power_point_value = -1;
}
/* copy real pp_list to our local copy and check length */
void pplist_retrieve(){
for (int i = 0; i < 20; i++)
{
g_pplist[i] = g_real_pplist[i];
}
/* in the general case your pplist will be full from the start so this is optimal */
g_pplist_idx = 19;
while ( g_pplist[g_pplist_idx] == -1 )
{
if ( g_pplist_idx == 0 )
return;
g_pplist_idx--;
}
g_pplist_idx++;
}
void (*real_pfree_pplist_init)();
void hook_pfree_pplist_init(){
__asm("push eax");
__asm("push ebx");
__asm("lea ebx, [eax+0x1C4]\n");
__asm("mov %0, ebx\n":"=m"(g_real_pplist));
__asm("call %0\n"::"a"(pplist_retrieve));
__asm("pop ebx");
__asm("pop eax");
real_pfree_pplist_init();
}
void (*real_pfree_pplist_inject)();
void hook_pfree_pplist_inject(){
__asm("lea esi, %0\n"::"m"(g_pplist[g_pplist_idx]));
__asm("mov dword ptr [esp+0x40], esi\n");
__asm("lea esi, %0\n"::"m"(g_pplist));
__asm("mov eax, dword ptr [esp+0x3C]\n");
__asm("mov %0, eax\n":"=m"(allocated_pplist_copy));
__asm("mov dword ptr [esp+0x3C], esi\n");
__asm("movzx eax, %0\n"::"m"(g_pplist_idx));
real_pfree_pplist_inject();
}
/* restore original pointer so that it can be freed */
void (*real_pfree_pplist_inject_cleanup)();
void hook_pfree_pplist_inject_cleanup()
{
__asm("mov esi, %0\n"::"m"(allocated_pplist_copy));
__asm("call %0\n"::"a"(pplist_reset));
real_pfree_pplist_inject_cleanup();
}
/* hook is installed in stage increment function */
void (*real_pfree_cleanup)();
void hook_pfree_cleanup()
{
__asm("push eax");
__asm("call %0"::"a"(popn22_is_normal_mode));
__asm("test al,al");
__asm("pop eax");
__asm("je skip_pfree_cleanup");
__asm("push esi\n");
__asm("push edi\n");
__asm("push eax\n");
__asm("push edx\n");
__asm("movzx eax, byte ptr [%0]\n"::"m"(g_pfree_song_offset));
__asm("movzx ebx, word ptr [%0]\n"::"m"(g_pfree_song_offset_2));
__asm("lea edi, dword ptr [esi+eax]\n");
__asm("lea esi, dword ptr [esi+ebx]\n");
__asm("push esi\n");
__asm("push edi\n");
/* compute powerpoints before cleanup */
__asm("sub eax, 0x20\n"); // eax still contains g_pfree_song_offset
__asm("neg eax\n");
__asm("lea eax, dword ptr [edi+eax]\n");
__asm("mov eax, dword ptr [eax]\n"); // music id (edi-0x38 or edi-0x34 depending on game)
__asm("cmp ax, 0xBB8\n"); // skip if music id is >= 3000 (cs_omni and user customs)
__asm("jae cleanup_score\n");
__asm("push 0\n");
__asm("push eax\n");
__asm("shr eax, 0x10\n"); //sheet id in al
__asm("call %0\n"::"b"(popn22_get_chart_level));
__asm("add esp, 8\n");
__asm("mov bl, byte ptr [edi+0x24]\n"); // medal
/* push "is full combo" param */
__asm("cmp bl, 8\n");
__asm("setae dl\n");
__asm("movzx ecx, dl\n");
__asm("push ecx\n");
/* push "is clear" param */
__asm("cmp bl, 4\n");
__asm("setae dl\n");
__asm("movzx ecx, dl\n");
__asm("push ecx\n");
__asm("mov ecx, eax\n"); // diff level
__asm("mov eax, dword ptr [edi]\n"); // score
__asm("call %0\n"::"b"(popn22_get_powerpoints));
__asm("mov %0, eax\n":"=a"(g_power_point_value):);
__asm("call %0\n"::"a"(pplist_update));
__asm("cleanup_score:\n");
/* can finally cleanup score */
__asm("pop edi\n");
__asm("pop esi\n");
__asm("mov ecx, 0x98");
__asm("rep movsd");
__asm("pop edx");
__asm("pop eax");
__asm("pop edi");
__asm("pop esi");
__asm("jmp cleanup_end");
__asm("skip_pfree_cleanup:\n");
real_pfree_cleanup();
__asm("cleanup_end:\n");
}
/* hook without the power point fixes (eclale best effort) */
void hook_pfree_cleanup_simple()
{
__asm("push eax");
__asm("call %0"::"a"(popn22_is_normal_mode));
__asm("test al,al");
__asm("pop eax");
__asm("je skip_pfree_cleanup_simple");
__asm("push esi\n");
__asm("push edi\n");
__asm("push eax\n");
__asm("push ebx\n");
__asm("push edx\n");
__asm("movsx eax, byte ptr [%0]\n"::"m"(g_pfree_song_offset));
__asm("movsx ebx, word ptr [%0]\n"::"m"(g_pfree_song_offset_2));
__asm("lea edi, dword ptr [esi+eax]\n");
__asm("lea esi, dword ptr [esi+ebx]\n");
__asm("mov ecx, 0x98");
__asm("rep movsd");
__asm("pop edx");
__asm("pop ebx");
__asm("pop eax");
__asm("pop edi");
__asm("pop esi");
__asm("ret");
__asm("skip_pfree_cleanup_simple:\n");
real_pfree_cleanup();
}
static bool patch_pfree() {
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
bool simple = false;
pplist_reset();
/* retrieve is_normal_mode function */
{
int64_t pattern_offset = _search(data, dllSize, "\x83\xC4\x0C\x33\xC0\xC3\xCC\xCC\xCC\xCC\xE8", 11, 0);
if (pattern_offset == -1) {
LOG("popnhax: pfree: cannot find is_normal_mode function, fallback to best effort (active in all modes)\n");
}
else
{
popn22_is_normal_mode = (bool(*)()) (data + pattern_offset + 0x0A);
}
}
/* stop stage counter (2 matches, 1st one is the good one) */
{
int64_t pattern_offset = _search(data, dllSize, "\x83\xF8\x04\x77\x3E", 5, 0);
if (pattern_offset == -1) {
LOG("couldn't find stop stage counter\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset - 0x05;
g_stage_addr = *(uint32_t*)(patch_addr+1);
/* hook to retrieve address for exit to thank you for playing screen */
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)hook_stage_update,
(void **)&real_stage_update);
}
/* retrieve memory zone parameters to prepare for cleanup */
char offset_from_base = 0x00;
char offset_from_stage1[2] = {0x00, 0x00};
int64_t child_fun_loc = 0;
{
int64_t offset = _search(data, dllSize, "\x8D\x46\xFF\x83\xF8\x0A\x0F", 7, 0);
if (offset == -1) {
#if DEBUG == 1
LOG("popnhax: pfree: failed to retrieve struct size and offset\n");
#endif
/* best effort for older games compatibility (works with eclale) */
offset_from_base = 0x54;
offset_from_stage1[0] = 0x04;
offset_from_stage1[1] = 0x05;
simple = true;
goto pfree_apply;
}
uint32_t child_fun_rel = *(uint32_t *) ((int64_t)data + offset - 0x04);
child_fun_loc = offset + child_fun_rel;
}
{
int64_t pattern_offset = _search(data, 0x40, "\xCB\x69", 2, child_fun_loc);
if (pattern_offset == -1) {
LOG("popnhax: pfree: failed to retrieve offset from stage1 (child_fun_loc = %llx\n",child_fun_loc);
return false;
}
offset_from_stage1[0] = *(uint8_t *) ((int64_t)data + pattern_offset + 0x03);
offset_from_stage1[1] = *(uint8_t *) ((int64_t)data + pattern_offset + 0x04);
#if DEBUG == 1
LOG("popnhax: pfree: offset_from_stage1 is %02x %02x\n",offset_from_stage1[0],offset_from_stage1[1]);
#endif
}
{
int64_t pattern_offset = _search(data, 0x40, "\x8d\x74\x01", 3, child_fun_loc);
if (pattern_offset == -1) {
LOG("popnhax: pfree: failed to retrieve offset from base\n");
return false;
}
offset_from_base = *(uint8_t *) ((int64_t)data + pattern_offset + 0x03);
#if DEBUG == 1
LOG("popnhax: pfree: offset_from_base is %02x\n",offset_from_base);
#endif
}
pfree_apply:
g_pfree_song_offset = offset_from_base;
g_pfree_song_offset_2 = *((uint16_t*)offset_from_stage1);
g_pfree_song_offset_2 += offset_from_base;
/* cleanup score and stats */
{
int64_t pattern_offset = _search(data, dllSize, "\xFE\x46\x0E\x80", 4, 0);
if (pattern_offset == -1) {
LOG("popnhax: pfree: cannot find stage update function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
/* replace stage number increment with a score cleanup function */
if ( simple )
{
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)hook_pfree_cleanup_simple,
(void **)&real_pfree_cleanup);
LOG("popnhax: premium free enabled (WARN: no power points fix)\n");
return true;
}
/* compute and save power points to g_pplist before cleaning up memory zone */
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)hook_pfree_cleanup,
(void **)&real_pfree_cleanup);
}
/* fix power points */
{
int64_t pattern_offset = _search(data, dllSize, "\x8A\xD8\x8B\x44\x24\x0C\xE8", 7, 0);
if (pattern_offset == -1) {
LOG("popnhax: pfree: cannot find get_power_points function\n");
return false;
}
popn22_get_chart_level = (void(*)()) (data + pattern_offset - 0x07);
}
{
int64_t pattern_offset = _search(data, dllSize, "\x3D\x50\xC3\x00\x00\x7D\x05", 7, 0);
if (pattern_offset == -1) {
LOG("popnhax: pfree: cannot find get_power_points function\n");
return false;
}
popn22_get_powerpoints = (void(*)()) (data + pattern_offset);
}
/* init pp_list */
{
int64_t pattern_offset = _search(data, dllSize, "\x6B\xD2\x64\x2B\xCA\x51\x50\x68", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: pfree: cannot find power point load function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset - 0x1A;
/* copy power point list to g_pplist on profile load and init g_pplist_idx */
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)hook_pfree_pplist_init,
(void **)&real_pfree_pplist_init);
}
/* inject pp_list at end of credit */
{
int64_t pattern_offset = _search(data, dllSize, "\x8B\x74\x24\x3C\x66\x8B\x04\x9E", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: pfree: cannot find end of credit power point handling function (1)\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset - 0x07;
/* make power point list pointers point to g_pplist at the end of processing */
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)hook_pfree_pplist_inject,
(void **)&real_pfree_pplist_inject);
}
/* restore pp_list pointer so that it is freed at end of credit */
{
int64_t pattern_offset = _search(data, dllSize, "\x7E\x04\x2B\xC1\x8B\xF8\x3B\xF5", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: pfree: cannot find end of credit power point handling function (2)\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x06;
/* make power point list pointers point to g_pplist at the end of processing */
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)hook_pfree_pplist_inject_cleanup,
(void **)&real_pfree_pplist_inject_cleanup);
}
LOG("popnhax: premium free enabled\n");
return true;
}
static bool patch_quick_retire(bool pfree)
{
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
if ( !pfree )
{
/* pfree already installs this hook
*/
{
int64_t pattern_offset = _search(data, dllSize, "\x83\xF8\x04\x77\x3E", 5, 0);
if (pattern_offset == -1) {
LOG("couldn't find stop stage counter\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset - 0x05;
g_stage_addr = *(uint32_t*)(patch_addr+1);
/* hook to retrieve address for exit to thank you for playing screen */
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)hook_stage_update,
(void **)&real_stage_update);
}
/* prevent stage number increment when going back to song select without pfree */
if (config.back_to_song_select)
{
int64_t pattern_offset = _search(data, dllSize, "\xFE\x46\x0E\x80", 4, 0);
if (pattern_offset == -1) {
LOG("popnhax: quick retire: cannot find stage update function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
/* hook to retrieve address for exit to thank you for playing screen */
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)hook_stage_increment,
(void **)&real_stage_increment);
}
/* pfree already retrieves this function
*/
{
int64_t pattern_offset = _search(data, dllSize, "\x83\xC4\x0C\x33\xC0\xC3\xCC\xCC\xCC\xCC\xE8", 11, 0);
if (pattern_offset == -1) {
LOG("popnhax: quick retire: cannot find is_normal_mode function, fallback to best effort (active in all modes)\n");
}
else
{
popn22_is_normal_mode = (bool(*)()) (data + pattern_offset + 0x0A);
}
}
}
/* instant retire with numpad 9 in song */
{
int64_t pattern_offset = _search(data, dllSize, "\x55\x8B\xEC\x83\xE4\xF8\x83\xEC\x08\x0F\xBF\x05", 12, 0);
if (pattern_offset == -1) {
LOG("popnhax: cannot retrieve song loop\n");
return false;
}
if (!get_addr_icca(&g_addr_icca))
{
LOG("popnhax: cannot retrieve ICCA address for numpad hook\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)quickexit_game_loop,
(void **)&real_game_loop);
}
{
// PlaySramSound func
int64_t pattern_offset = _search(data, dllSize,
"\x51\x56\x8B\xF0\x85\xF6\x74\x6C\x6B\xC0\x2C", 11, 0);
if (pattern_offset == -1) {
LOG("popnhax: PlaySramSound_addr was not found.\n");
return false;
}
playsramsound_func = (uint32_t)((int64_t)data + pattern_offset);
}
/* instant exit with numpad 9 on result screen */
{
int64_t first_loc = _search(data, dllSize, "\xBF\x03\x00\x00\x00\x81\xC6", 7, 0);
if (first_loc == -1) {
LOG("popnhax: cannot retrieve result screen loop first loc\n");
return false;
}
int64_t pattern_offset = _search(data, 0x50, "\x55\x8B\xEC\x83\xE4", 5, first_loc-0x50);
if (pattern_offset == -1) {
LOG("popnhax: cannot retrieve result screen loop\n");
return false;
}
if (!get_addr_icca(&g_addr_icca))
{
LOG("popnhax: cannot retrieve ICCA address for numpad hook\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)quickexit_result_loop,
(void **)&real_result_loop);
}
/* no need to press red button when numpad 8 or 9 is pressed on result screen */
{
int64_t pattern_offset = _search(data, dllSize, "\x84\xC0\x75\x0F\x8B\x8D\x1C\x0A\x00\x00\xE8", 11, 0);
int adjust = 0;
if (pattern_offset == -1) {
/* fallback */
pattern_offset = _search(data, dllSize, "\x09\x00\x84\xC0\x75\x0F\x8B\x8D", 8, 0);
adjust = 2;
if (pattern_offset == -1) {
LOG("popnhax: cannot retrieve result screen button check\n");
return false;
}
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x1A + adjust;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)quickexit_result_button_loop,
(void **)&real_result_button_loop);
}
{
/* retrieve current stage score addr for cleanup (also used to fix quick retire medal) */
int64_t pattern_offset = _search(data, dllSize, "\xF3\xA5\x5F\x5E\x5B\xC2\x04\x00", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: quick retire: cannot retrieve score addr\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset - 5;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)quickretry_retrieve_score,
(void **)&real_retrieve_score);
}
LOG("popnhax: quick retire enabled\n");
/* retrieve songstart function pointer for quick retry */
{
int64_t pattern_offset = _search(data, dllSize, "\xE9\x0C\x01\x00\x00\x8B\x85", 7, 0);
int delta = -4;
if (pattern_offset == -1) {
delta = 18;
pattern_offset = _search(data, dllSize, "\x6A\x00\xB8\x17\x00\x00\x00\xE8", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: quick retry: cannot retrieve song start function\n");
return false;
}
}
uint64_t patch_addr = (int64_t)data + pattern_offset + delta;
g_startsong_addr = *(uint32_t*)(patch_addr);
}
/* instant retry (go back to option select) with numpad 8 */
{
/* hook quick retire transition to go back to option select instead */
int64_t pattern_offset = _search(data, dllSize, "\x8B\xE8\x8B\x47\x30\x83\xF8\x17", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: quick retry: cannot retrieve screen transition function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)quickexit_screen_transition,
(void **)&real_screen_transition);
}
/* instant launch song with numpad 8 on option select (hold 8 during song for quick retry)
* there are now 3 patches: transition between song select and option select goes like A->B->C with C being the option select screen
* because of special behavior, ac tsumtsum goes A->C so I cannot keep the patch in B else tsumtsum gets stuck in a never ending option select loop
* former patch in B now sets a flag in A which is processed in B, then C also processes it in case the flag is still there */
{
int64_t pattern_offset = _search(data, dllSize, "\xE4\xF8\x51\x56\x8B\xF1\x80\xBE", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: quick retry: cannot retrieve option screen loop\n");
return false;
}
if (!get_addr_icca(&g_addr_icca))
{
LOG("popnhax: quick retry: cannot retrieve ICCA address for numpad hook\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset - 0x04;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)quickexit_option_screen,
(void **)&real_option_screen);
}
{
int64_t pattern_offset = _search(data, dllSize, "\x8B\xF0\x83\x7E\x0C\x00\x0F\x84", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: quick retry: cannot retrieve option screen loop\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset - 0x0F;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)quickexit_option_screen_apply_skip,
(void **)&real_option_screen_apply_skip);
}
{
int64_t pattern_offset = _search(data, dllSize, "\x0A\x00\x00\x83\x78\x34\x00\x75\x3D\xB8", 10, 0); //unilab
uint8_t adjust = 15;
g_transition_offset = 0xA10;
if (pattern_offset == -1) {
/* fallback */
pattern_offset = _search(data, dllSize, "\x8B\x85\x0C\x0A\x00\x00\x83\x78\x34\x00\x75", 11, 0);
adjust = 12;
g_transition_offset = 0xA0C;
}
if (pattern_offset == -1) {
LOG("popnhax: quick retry: cannot retrieve option screen loop function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset - adjust;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)quickexit_option_screen_apply_skip_tsum,
(void **)&real_option_screen_apply_skip_tsum);
}
if (config.back_to_song_select)
{
char filepath[64];
if (sprintf(filepath, "/data/sd/system/system%d/system%d.2dx", config.game_version, config.game_version) <= 0){
LOG("popnhax: back to song select: cannot build systemxx.2dx filepath string.\n");
return false;
}
g_system2dx_filepath = strdup(filepath);
if (g_system2dx_filepath == NULL){
LOG("popnhax: back to song select: cannot allocate systemxx.2dx filepath string.\n");
return false;
}
{
// loadnew2dx func
int64_t pattern_offset = _search(data, dllSize,
"\x53\x55\x8B\x6C\x24\x0C\x56\x57\x8B\xCD", 10, 0);
if (pattern_offset == -1) {
LOG("popnhax: loadnew2dx_addr was not found.\n");
return false;
}
loadnew2dx_func = (uint32_t)((int64_t)data + pattern_offset);
}
{
// playgeneralsound func
int64_t pattern_offset = _search(data, dllSize,
"\x33\xC0\x5B\xC3\xCC\xCC\xCC\xCC\xCC\x55\x8B\xEC\x83\xE4\xF8", 15, 0);
if (pattern_offset == -1) {
LOG("popnhax: playgeneralsound_addr was not found.\n");
return false;
}
playgeneralsound_func = (uint32_t)((int64_t)data + pattern_offset + 9);
}
/* prevent "here we go" sound from playing when going back to song select (3 occurrences) */
{
LPVOID hook[3] = { (LPVOID)backtosongselect_herewego1, (LPVOID)backtosongselect_herewego2, (LPVOID)backtosongselect_herewego3 };
void** real[3] = { (void**)&real_backtosongselect_herewego1, (void**)&real_backtosongselect_herewego2, (void**)&real_backtosongselect_herewego3 };
int64_t pattern_offset = 0;
int i = 0;
do {
pattern_offset = _search(data, dllSize-pattern_offset-10, "\x6A\x00\xB8\x17\x00\x00\x00\xE8", 8, pattern_offset+10);
if (pattern_offset == -1) {
LOG("popnhax: cannot find \"here we go\" sound play (occurrence %d).\n",i+1);
} else {
uint64_t patch_addr = (int64_t)data + pattern_offset + 7;
_MH_CreateHook((LPVOID)patch_addr, hook[i],real[i]);
i++;
}
} while (i < 3 && pattern_offset != -1);
}
/* go back to song select with numpad 9 on song option screen (before pressing yellow) */
{
int64_t pattern_offset = -1;
uint8_t adjust = 0;
if (config.game_version < 27) {
pattern_offset = _search(data, dllSize, "\x8B\x85\x0C\x0A\x00\x00\x83\x78\x34\x00\x75", 11, 0);
adjust = 0;
} else if (config.game_version == 27) {
pattern_offset = _search(data, dllSize, "\x0A\x00\x00\x83\x78\x34\x00\x75\x3D\xB8", 10, 0);
adjust = 3;
} else { // let's hope for the future
pattern_offset = _search(data, dllSize, "\x8B\x85\x10\x0A\x00\x00\x83\x78\x34\x00\x75", 11, 0);
adjust = 0;
}
if (pattern_offset == -1) {
LOG("popnhax: back to song select: cannot retrieve option screen loop function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset - adjust;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)backtosongselect_option_screen,
(void **)&real_option_screen_later);
}
/* automatically leave option screen after numpad 9 press */
{
int64_t pattern_offset = -1;
uint8_t adjust = 0;
if ( config.game_version <= 27 ) {
pattern_offset = _search(data, dllSize, "\x0A\x00\x00\x83\xC0\x04\xBF\x0C\x00\x00\x00\xE8", 12, 0);
adjust = 7;
} else {
pattern_offset = _search(data, dllSize, "\x84\xC0\x0F\x85\x91\x00\x00\x00\x8B", 9, 0);
adjust = 0;
}
if (pattern_offset == -1) {
LOG("popnhax: back to song select: cannot retrieve option screen loop function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset - adjust;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)backtosongselect_option_screen_auto_leave,
(void **)&real_backtosongselect_option_screen_auto_leave);
}
/* go back to song select with numpad 9 on song option screen (after pressing yellow) */
{
int64_t pattern_offset = _search(data, dllSize, "\x0A\x00\x00\x83\x78\x38\x00\x75\x3D\x68", 10, 0); //unilab
uint8_t adjust = 3;
if (pattern_offset == -1) {
/* fallback */
pattern_offset = _search(data, dllSize, "\x8B\x85\x0C\x0A\x00\x00\x83\x78\x38\x00\x75", 11, 0);
adjust = 0;
}
if (pattern_offset == -1) {
LOG("popnhax: quick retry: cannot retrieve yellow option screen loop function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset - adjust;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)backtosongselect_option_yellow,
(void **)&real_option_screen_yellow);
}
/* automatically leave after numpad 9 press */
{
int64_t pattern_offset = _search(data, dllSize, "\x8B\x55\x00\x8B\x82\x9C\x00\x00\x00\x6A\x01\x8B\xCD\xFF\xD0\x80\xBD", 17, 0);
if (pattern_offset == -1) {
LOG("popnhax: back to song select: cannot retrieve option screen yellow leave addr\n");
return false;
}
g_option_yellow_leave_addr = (int32_t)data + pattern_offset - 0x05;
pattern_offset = _search(data, dllSize, "\x84\xC0\x0F\x84\xF1\x00\x00\x00\x8B\xC5", 10, 0);
if (pattern_offset == -1) {
LOG("popnhax: back to song select: cannot retrieve option screen yellow button check function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)backtosongselect_option_screen_yellow_auto_leave,
(void **)&real_backtosongselect_option_screen_yellow_auto_leave);
}
LOG("popnhax: quick retire: return to song select enabled\n");
}
if (pfree)
LOG("popnhax: quick retry enabled\n");
return true;
}
static bool patch_add_to_base_offset(int8_t delta) {
int32_t new_value = delta;
char *as_hex = (char *) &new_value;
LOG("popnhax: base offset: adding %d to base offset.\n",delta);
/* call get_addr_timing_offset() so that it can still work after timing value is overwritten */
uint32_t original_timing;
get_addr_timing_offset(&original_timing);
uint32_t sd_timing_addr;
if (!get_addr_sd_timing(&sd_timing_addr))
{
LOG("popnhax: base offset: cannot find base SD timing\n");
return false;
}
int32_t current_value = *(int32_t *) sd_timing_addr;
new_value = current_value+delta;
patch_memory(sd_timing_addr, as_hex, 4);
LOG("popnhax: base offset: SD offset is now %d.\n",new_value);
uint32_t hd_timing_addr;
if (!get_addr_hd_timing(&hd_timing_addr))
{
LOG("popnhax: base offset: cannot find base HD timing\n");
return false;
}
current_value = *(int32_t *) hd_timing_addr;
new_value = current_value+delta;
patch_memory(hd_timing_addr, as_hex, 4);
LOG("popnhax: base offset: HD offset is now %d.\n",new_value);
return true;
}
bool g_hardware_offload = false;
bool g_enhanced_poll_ready = false;
int (*usbPadRead)(uint32_t*);
uint32_t g_poll_rate_avg = 0;
uint32_t g_last_button_state = 0;
uint8_t g_debounce = 0;
int32_t g_button_state[9] = {0};
static unsigned int __stdcall enhanced_polling_stats_proc(void *ctx)
{
HMODULE hinstLib = GetModuleHandleA("ezusb.dll");
usbPadRead = (int(*)(uint32_t*))GetProcAddress(hinstLib, "?usbPadRead@@YAHPAK@Z");
static uint8_t button_debounce[9] = {0};
for (int i=0; i<9; i++)
{
g_button_state[i] = -1;
}
while (!g_enhanced_poll_ready)
{
Sleep(500);
}
if (config.enhanced_polling_priority)
{
SetThreadPriority(GetCurrentThread(), config.enhanced_polling_priority);
LOG("[Enhanced polling] Thread priority set to %d\n", GetThreadPriority(GetCurrentThread()));
}
uint32_t count = 0;
uint32_t count_time = 0;
uint32_t curr_poll_time = 0;
uint32_t prev_poll_time = 0;
while (g_enhanced_poll_ready)
{
uint32_t pad_bits;
/* ensure at least 1ms has elapsed between polls
* (beware of SD cab hardware compatibility)
*/
curr_poll_time = timeGetTime();
if (curr_poll_time == prev_poll_time)
{
curr_poll_time++;
Sleep(1);
}
prev_poll_time = curr_poll_time;
if (count == 0)
{
count_time = curr_poll_time;
}
usbPadRead(&pad_bits);
g_last_button_state = pad_bits;
unsigned int buttonState = (g_last_button_state >> 8) & 0x1FF;
for (int i = 0; i < 9; i++)
{
if ( ((buttonState >> i)&1) )
{
if (g_button_state[i] == -1)
{
g_button_state[i] = curr_poll_time;
}
//debounce on release (since we're forced to stub usbPadReadLast)
button_debounce[i] = g_debounce;
}
else
{
if (button_debounce[i]>0)
{
button_debounce[i]--;
}
if (button_debounce[i] == 0)
{
g_button_state[i] = -1;
}
else
{
//debounce ongoing: flag button as still pressed
g_last_button_state |= 1<<(8+i);
}
}
}
count++;
if (count == 100)
{
g_poll_rate_avg = timeGetTime() - count_time;
count = 0;
}
}
return 0;
}
static unsigned int __stdcall enhanced_polling_proc(void *ctx)
{
HMODULE hinstLib = GetModuleHandleA("ezusb.dll");
usbPadRead = (int(*)(uint32_t*))GetProcAddress(hinstLib, "?usbPadRead@@YAHPAK@Z");
static uint8_t button_debounce[9] = {0};
for (int i=0; i<9; i++)
{
g_button_state[i] = -1;
}
while (!g_enhanced_poll_ready)
{
Sleep(500);
}
if (config.enhanced_polling_priority)
{
SetThreadPriority(GetCurrentThread(), config.enhanced_polling_priority);
LOG("[Enhanced polling] Thread priority set to %d\n", GetThreadPriority(GetCurrentThread()));
}
uint32_t curr_poll_time = 0;
uint32_t prev_poll_time = 0;
while (g_enhanced_poll_ready)
{
uint32_t pad_bits;
/* ensure at least 1ms has elapsed between polls
* (beware of SD cab hardware compatibility)
*/
curr_poll_time = timeGetTime();
if (curr_poll_time == prev_poll_time)
{
curr_poll_time++;
Sleep(1);
}
prev_poll_time = curr_poll_time;
usbPadRead(&pad_bits);
g_last_button_state = pad_bits;
unsigned int buttonState = (g_last_button_state >> 8) & 0x1FF;
for (int i = 0; i < 9; i++)
{
if ( ((buttonState >> i)&1) )
{
if (g_button_state[i] == -1)
{
g_button_state[i] = curr_poll_time;
}
//debounce on release (since we're forced to stub usbPadReadLast)
button_debounce[i] = g_debounce;
}
else
{
if (button_debounce[i]>0)
{
button_debounce[i]--;
}
if (button_debounce[i] == 0)
{
g_button_state[i] = -1;
}
else
{
//debounce ongoing: flag button as still pressed
g_last_button_state |= 1<<(8+i);
}
}
}
}
return 0;
}
uint32_t buttonGetMillis(uint8_t button)
{
if (g_button_state[button] == -1)
return 0;
uint32_t but = g_button_state[button];
uint32_t curr = timeGetTime();
if (but <= curr)
{
uint32_t res = curr - but;
return res;
}
return 0;
}
/* popnio.dll functions (for hardware offload) */
bool (__cdecl *openrealio)(void);
uint16_t (__cdecl *getbuttonstate)(uint8_t);
int (__cdecl *popnio_usbpadread)(uint32_t *);
int (__cdecl *popnio_usbpadreadlast)(uint8_t *);
uint32_t usbPadReadHook_addr = 0;
uint32_t usbPadReadLastHook_addr = 0;
int usbPadReadHook(uint32_t *pad_bits)
{
// if we're here then ioboard is ready
g_enhanced_poll_ready = true;
// return last known input
*pad_bits = g_last_button_state;
return 0;
}
int usbPadReadHookHO(uint32_t *pad_bits)
{
int res = popnio_usbpadread(pad_bits); // faster = more up-to-date
// update last_button_state (for enhanced polling stats display)
g_last_button_state = *pad_bits;
return res;
}
uint16_t HO_get_button_timer(uint8_t index)
{
uint16_t val = getbuttonstate(index);
if ( val == 0xffff )
return 0;
return val;
}
uint32_t g_offset_fix[9] = {0};
uint8_t g_poll_index = 0;
void (*real_enhanced_poll)();
void patch_enhanced_poll() {
/* eax contains button being checked [0-8]
* esi contains delta about to be evaluated
* we need to do esi -= buttonGetMillis([%eax]); to fix the offset accurately */
__asm("push edx\n");
__asm("cmp byte ptr [_g_hardware_offload], 0\n");
__asm("je skip_ho\n");
/* hardware offload */
__asm("push ecx\n");
__asm("push edx\n");
__asm("push eax\n");
__asm("call %P0" : : "i"(HO_get_button_timer));
__asm("movzx ebx, ax\n");
__asm("pop eax\n");
__asm("pop edx\n");
__asm("pop ecx\n");
__asm("jmp button_state_empty\n");
/* no hardware offload */
__asm("skip_ho:\n");
__asm("mov %0, al\n":"=m"(g_poll_index): :);
/* reimplem buttonGetMillis(), result in ebx */
__asm("xor ebx,ebx\n");
__asm("mov edx,dword ptr ds:[eax*4+%0]\n"::"d"(g_button_state));
__asm("cmp edx,0xFFFFFFFF\n");
__asm("je button_state_empty\n");
__asm("push ecx\n");
__asm("push edx\n");
__asm("call %P0" : : "i"(timeGetTime));
__asm("pop edx\n");
__asm("pop ecx\n");
__asm("cmp edx,eax\n");
__asm("jbe button_has_been_pressed\n");
__asm("restore_eax:\n");
__asm("movzx eax, %0\n": :"d"(g_poll_index));
/* leave buttonGetMillis */
__asm("button_state_empty:\n");
__asm("sub esi, ebx\n"); // actual delta correction
__asm("lea edx,dword ptr [eax*4+%0]\n"::"d"(g_offset_fix));
__asm("mov [edx], ebx\n"::); // save correction value in g_offset_fix[g_poll_index];
__asm("pop edx\n");
__asm("mov eax, %0\n" : : "i"(&real_enhanced_poll));
__asm("jmp [eax]");
__asm("button_has_been_pressed:\n");
__asm("sub eax,edx\n"); // eax = correction value (currTime - g_button_state[g_poll_index]);
__asm("mov ebx, eax\n");// put correction value in ebx
__asm("jmp restore_eax\n");
}
char enhancedusbio_str[] = "ENHANCED USB I/O";
static bool patch_usbio_string()
{
uint32_t string_addr = (uint32_t)enhancedusbio_str;
const char* as_hex = (const char*) &string_addr;
/* change USB I/O to "ENHANCED USB I/O" */
if (!find_and_patch_hex(g_game_dll_fn, "\x3F\x41\x56\x43\x41\x70\x70\x40\x40\x00\x00", 11, 0x0B, as_hex, 4))
{
LOG("WARNING: PopnIO: cannot replace USB I/O string in selftest menu\n");
return false;
}
return true;
}
static bool patch_enhanced_polling_hardware_setup()
{
bool res = false;
HMODULE hinstLib = LoadLibrary("popnio.dll");
if (hinstLib == NULL) {
auto err = GetLastError();
if ( err != 126 )
LOG("ERROR: unable to load popnio.dll for hardware offload enhanced polling (error %ld)\n",err);
return res;
}
LOG("popnhax: enhanced_polling: popnio.dll found, setup hardware offload\n");
// Get functions pointers
openrealio = (bool (__cdecl *)(void))GetProcAddress(hinstLib, "open_realio");
getbuttonstate = (uint16_t (__cdecl *)(uint8_t))GetProcAddress(hinstLib, "get_button_state");
popnio_usbpadread = (int (__cdecl *)(uint32_t *))GetProcAddress(hinstLib, "fast_usbPadRead");
popnio_usbpadreadlast = (int (__cdecl *)(uint8_t *))GetProcAddress(hinstLib, "fast_usbPadReadLast");
if ( openrealio == NULL || getbuttonstate == NULL || popnio_usbpadread == NULL || popnio_usbpadreadlast == NULL )
{
LOG("ERROR: a required function was not found in popnio.dll\n");
goto cleanup;
}
if ( !openrealio() )
{
LOG("ERROR: PopnIO: device not found (make sure it is connected in REALIO mode and that the FX2LP driver is installed)\n");
goto cleanup;
}
res = true;
cleanup:
return res;
}
static HANDLE enhanced_polling_thread;
static bool patch_enhanced_polling(uint8_t debounce, bool stats)
{
g_debounce = debounce;
if ( !g_hardware_offload && enhanced_polling_thread == NULL) {
if (stats)
{
enhanced_polling_thread = (HANDLE) _beginthreadex(
NULL,
0,
enhanced_polling_stats_proc,
NULL,
0,
NULL);
}
else
{
enhanced_polling_thread = (HANDLE) _beginthreadex(
NULL,
0,
enhanced_polling_proc,
NULL,
0,
NULL);
}
} // thread will remain dormant while g_enhanced_poll_ready == false
/* patch eval timing function to fix offset depending on how long ago the button was pressed */
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
{
int64_t pattern_offset = _search(data, dllSize, "\xC6\x44\x24\x0C\x00\xE8", 6, 0);
if (pattern_offset == -1) {
LOG("popnhax: enhanced polling: cannot find eval timing function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x05;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)patch_enhanced_poll,
(void **)&real_enhanced_poll); // substract
}
/* patch calls to usbPadRead and usbPadReadLast */
{
int64_t pattern_offset = _search(data, dllSize, "\x83\xC4\x04\x5D\xC3\xCC\xCC", 7, 0);
if (pattern_offset == -1) {
LOG("popnhax: enhanced polling: cannot find usbPadRead call (1)\n");
return false;
}
pattern_offset = _search(data, dllSize-pattern_offset-1, "\x83\xC4\x04\x5D\xC3\xCC\xCC", 7, pattern_offset+1);
if (pattern_offset == -1) {
LOG("popnhax: enhanced polling: cannot find usbPadRead call (2)\n");
return false;
}
if ( !g_hardware_offload )
{
usbPadReadHook_addr = (uint32_t)&usbPadReadHook;
void *addr = (void *)&usbPadReadHook_addr;
uint32_t as_int = (uint32_t)addr;
// game will call usbPadReadHook instead of real usbPadRead (function call hook in order not to interfere with tools)
uint64_t patch_addr = (int64_t)data + pattern_offset - 0x04; // usbPadRead function address
patch_memory(patch_addr, (char*)&as_int, 4);
// don't call usbPadReadLast as it messes with the 1000Hz polling, we'll be running our own debouncing instead
patch_addr = (int64_t)data + pattern_offset - 20;
patch_memory(patch_addr, (char*)"\x90\x90\x90\x90\x90\x90", 6);
} else {
// game will call popnio.dll fast_usbPadReadLast instead of the real usbPadReadLast
usbPadReadLastHook_addr = (uint32_t)popnio_usbpadreadlast;
void *addr = (void *)&usbPadReadLastHook_addr;
uint32_t as_int = (uint32_t)addr;
uint64_t patch_addr = (int64_t)data + pattern_offset - 18; // usbPadReadLast function address
patch_memory(patch_addr, (char*)&as_int, 4);
// game will call call popnio.dll fast_usbPadRead instead of the real usbPadRead
usbPadReadHook_addr = (stats) ? (uint32_t)&usbPadReadHookHO : (uint32_t)popnio_usbpadread;
addr = (void *)&usbPadReadHook_addr;
as_int = (uint32_t)addr;
patch_addr = (int64_t)data + pattern_offset - 0x04; // usbPadRead function address
patch_memory(patch_addr, (char*)&as_int, 4);
}
}
LOG("popnhax: enhanced polling enabled");
if ( g_hardware_offload )
LOG(" (with hardware offload)");
else if (g_debounce != 0)
LOG(" (%u ms debounce)", g_debounce);
LOG("\n");
return true;
}
void (*real_chart_load)();
void patch_chart_load_old() {
/* This is mostly the same patch as the new version, except :
* - eax and ecx have a different interpretation
* - a chart chunk is only 2 dwords
*/
__asm("cmp word ptr [edi+eax*8+4], 0x245\n");
__asm("jne old_patch_chart_load_end\n");
/* keysound event has been found, we need to convert timestamp */
__asm("mov word ptr [edi+eax*8+4], 0x745\n"); // we'll undo it if we cannot apply it
__asm("push eax\n"); // we'll convert to store chunk pointer (keysound timestamp at beginning)
__asm("push ebx\n"); // we'll store a copy of our chunk pointer there
__asm("push edx\n"); // we'll store the button info there
__asm("push esi\n"); // we'll store chart growth for part2 subroutine there
__asm("push ecx\n"); // we'll convert to store remaining chunks
/* Convert eax,ecx registers to same format as later games so the rest of the patch is similar
* TODO: factorize
*/
__asm("sub ecx, eax\n"); // ecx is now remaining chunks rather than chart size
__asm("imul eax, 8\n");
__asm("add eax, edi\n"); //now eax is chunk pointer
/* PART1: check button associated with keysound, then look for next note event for this button */
__asm("xor dx, dx\n");
__asm("mov dl, byte ptr [eax+7]\n");
__asm("shr edx, 4\n"); //dl now contains button value ( 00-08 )
__asm("mov ebx, eax\n"); //save chunk pointer into ebx for our rep movsd when a match is found
__asm("old_next_chart_chunk:\n");
__asm("add eax, 0x8\n"); // +0x08 in old chart format
__asm("sub ecx, 1\n");
__asm("jz old_end_of_chart\n");
/* check where the keysound is used */
__asm("cmp word ptr [eax+4], 0x245\n");
__asm("jne old_check_first_note_event\n"); //still need to check 0x145..
__asm("push ecx\n");
__asm("xor cx, cx\n");
__asm("mov cl, byte ptr [eax+7]\n");
__asm("shr ecx, 4\n"); //cl now contains button value ( 00-08 )
__asm("cmp cl, dl\n");
__asm("pop ecx");
__asm("jne old_check_first_note_event\n"); // 0x245 but not our button, still need to check 0x145..
//keysound change for this button before we found a key using it :(
__asm("pop ecx\n");
__asm("pop esi\n");
__asm("pop edx\n");
__asm("pop ebx\n");
__asm("pop eax\n");
__asm("mov word ptr [edi+eax*8+4], 0x0\n"); // disable operation, cannot be converted to a 0x745, and should not apply
__asm("jmp old_patch_chart_load_end\n");
__asm("old_check_first_note_event:\n");
__asm("cmp word ptr [eax+4], 0x145\n");
__asm("jne old_next_chart_chunk\n");
/* found note event */
__asm("cmp dl, byte ptr [eax+6]\n");
__asm("jne old_next_chart_chunk\n");
/* found MATCHING note event */
__asm("mov edx, dword ptr [ebx+4]\n"); //save operation (we need to shift the whole block first)
/* move the whole block just before the note event to preserve timestamp ordering */
__asm("push ecx\n");
__asm("push esi\n");
__asm("push edi\n");
__asm("mov edi, ebx\n");
__asm("mov esi, ebx\n");
__asm("add esi, 0x08\n");
__asm("mov ecx, eax\n");
__asm("sub ecx, 0x08\n");
__asm("sub ecx, ebx\n");
__asm("shr ecx, 2\n"); //div by 4 (sizeof dword)
__asm("rep movsd\n");
__asm("pop edi\n");
__asm("pop esi\n");
__asm("pop ecx\n");
/* write the 0x745 event just before our matching 0x145 which eax points to */
__asm("mov dword ptr [eax-0x08+0x04], edx\n"); // operation
__asm("mov edx, dword ptr [eax]\n");
__asm("mov dword ptr [eax-0x08], edx\n"); // timestamp
/* PART2: Look for other instances of same button key events (0x0145) before the keysound is detached */
__asm("xor esi, esi\n"); //init chart growth counter
/* look for next note event for same button */
__asm("mov ebx, dword ptr [eax-0x08+0x04]\n"); //operation copy
__asm("mov dl, byte ptr [eax+6]\n"); //dl now contains button value ( 00-08 )
__asm("old_next_same_note_chunk:\n");
__asm("add eax, 0x8\n");
__asm("sub ecx, 1\n");
__asm("jz old_end_of_same_note_search\n"); //end of chart reached
__asm("cmp word ptr [eax+4], 0x245\n");
__asm("jne old_check_if_note_event\n"); //still need to check 0x145..
__asm("push ecx\n");
__asm("xor cx, cx\n");
__asm("mov cl, byte ptr [eax+7]\n");
__asm("shr ecx, 4\n"); //cl now contains button value ( 00-08 )
__asm("cmp cl, dl\n");
__asm("pop ecx");
__asm("jne old_check_if_note_event\n"); // 0x245 but not our button, still need to check 0x145..
__asm("jmp old_end_of_same_note_search\n"); // found matching 0x245 (keysound change for this button), we can stop search
__asm("old_check_if_note_event:\n");
__asm("cmp word ptr [eax+4], 0x145\n");
__asm("jne old_next_same_note_chunk\n");
__asm("cmp dl, byte ptr [eax+6]\n");
__asm("jne old_next_same_note_chunk\n");
//found a match! time to grow the chart..
__asm("push ecx\n");
__asm("push esi\n");
__asm("push edi\n");
__asm("mov esi, eax\n");
__asm("mov edi, eax\n");
__asm("add edi, 0x08\n");
__asm("imul ecx, 0x08\n"); //ecx is number of chunks left, we want number of bytes for now, dword later
__asm("std\n");
__asm("add esi, ecx\n");
__asm("sub esi, 0x04\n"); //must be on very last dword from chart
__asm("add edi, ecx\n");
__asm("sub edi, 0x04\n");
__asm("shr ecx, 2\n"); //div by 4 (sizeof dword)
__asm("rep movsd\n");
__asm("cld\n");
__asm("pop edi\n");
__asm("pop esi\n");
__asm("pop ecx\n");
/* write the 0x745 event copy */
// timestamp is already correct as it's leftover from the 0x145
__asm("mov dword ptr [eax+0x04], ebx\n"); // operation
__asm("add esi, 1\n"); //increase growth counter
/* ebx still contains the 0x745 operation, dl still contains button, but eax points to 0x745 rather than the 0x145 so let's fix */
/* note that remaining chunks is still correct due to growth, so no need to decrement ecx */
__asm("add eax, 0x8\n");
__asm("jmp old_next_same_note_chunk\n"); //look for next occurrence
/* KEYSOUND PROCESS END */
__asm("old_end_of_same_note_search:\n");
/* restore before next timestamp */
__asm("pop ecx\n");
__asm("add ecx, esi\n"); // take chart growth into account
__asm("pop esi\n");
__asm("pop edx\n");
__asm("pop ebx\n");
__asm("pop eax\n");
/* next iteration should revisit the same block since we shifted... anticipate the +0xC/-1/+0x64 that will be done by real_chart_load() */
__asm("sub eax, 1\n");
__asm("sub dword ptr [edi+eax*8], 0x64\n");
__asm("jmp old_patch_chart_load_end\n");
__asm("old_end_of_chart:\n");
__asm("pop ecx\n");
__asm("pop esi\n");
__asm("pop edx\n");
__asm("pop ebx\n");
__asm("pop eax\n");
__asm("mov word ptr [edi+eax*8+4], 0x245\n"); // no match found (ad-lib keysound?), restore opcode
__asm("old_patch_chart_load_end:\n");
real_chart_load();
}
void patch_chart_load() {
__asm("cmp word ptr [eax+4], 0x245\n");
__asm("jne patch_chart_load_end\n");
/* keysound event has been found, we need to convert timestamp */
__asm("mov word ptr [eax+4], 0x745\n"); // we'll undo it if we cannot apply it
__asm("push eax\n"); // chunk pointer (keysound timestamp at beginning)
__asm("push ebx\n"); // we'll store a copy of our chunk pointer there
__asm("push edx\n"); // we'll store the button info there
__asm("push esi\n"); // we'll store chart growth for part2 subroutine there
__asm("push ecx\n"); // remaining chunks
/* PART1: check button associated with keysound, then look for next note event for this button */
__asm("xor dx, dx\n");
__asm("mov dl, byte ptr [eax+7]\n");
__asm("shr edx, 4\n"); //dl now contains button value ( 00-08 )
__asm("mov ebx, eax\n"); //save chunk pointer into ebx for our rep movsd when a match is found
__asm("next_chart_chunk:\n");
__asm("add eax, 0xC\n");
__asm("sub ecx, 1\n");
__asm("jz end_of_chart\n");
/* check where the keysound is used */
__asm("cmp word ptr [eax+4], 0x245\n");
__asm("jne check_first_note_event\n"); //still need to check 0x145..
__asm("push ecx\n");
__asm("xor cx, cx\n");
__asm("mov cl, byte ptr [eax+7]\n");
__asm("shr ecx, 4\n"); //cl now contains button value ( 00-08 )
__asm("cmp cl, dl\n");
__asm("pop ecx");
__asm("jne check_first_note_event\n"); // 0x245 but not our button, still need to check 0x145..
//keysound change for this button before we found a key using it :(
__asm("pop ecx\n");
__asm("pop esi\n");
__asm("pop edx\n");
__asm("pop ebx\n");
__asm("pop eax\n");
__asm("mov word ptr [eax+4], 0x0\n"); // disable operation, cannot be converted to a 0x745, and should not apply
__asm("jmp patch_chart_load_end\n");
__asm("check_first_note_event:\n");
__asm("cmp word ptr [eax+4], 0x145\n");
__asm("jne next_chart_chunk\n");
/* found note event */
__asm("cmp dl, byte ptr [eax+6]\n");
__asm("jne next_chart_chunk\n");
/* found MATCHING note event */
__asm("mov edx, dword ptr [ebx+4]\n"); //save operation (we need to shift the whole block first)
/* move the whole block just before the note event to preserve timestamp ordering */
__asm("push ecx\n");
__asm("push esi\n");
__asm("push edi\n");
__asm("mov edi, ebx\n");
__asm("mov esi, ebx\n");
__asm("add esi, 0x0C\n");
__asm("mov ecx, eax\n");
__asm("sub ecx, 0x0C\n");
__asm("sub ecx, ebx\n");
__asm("shr ecx, 2\n"); //div by 4 (sizeof dword)
__asm("rep movsd\n");
__asm("pop edi\n");
__asm("pop esi\n");
__asm("pop ecx\n");
/* write the 0x745 event just before our matching 0x145 which eax points to */
__asm("mov dword ptr [eax-0x0C+0x04], edx\n"); // operation
__asm("mov edx, dword ptr [eax]\n");
__asm("mov dword ptr [eax-0x0C], edx\n"); // timestamp
__asm("mov dword ptr [eax-0x0C+0x08], 0x0\n"); // cleanup possible longnote duration leftover
/* PART2: Look for other instances of same button key events (0x0145) before the keysound is detached */
__asm("xor esi, esi\n"); //init chart growth counter
/* look for next note event for same button */
__asm("mov ebx, dword ptr [eax-0x0C+0x04]\n"); //operation copy
__asm("mov dl, byte ptr [eax+6]\n"); //dl now contains button value ( 00-08 )
__asm("next_same_note_chunk:\n");
__asm("add eax, 0xC\n");
__asm("sub ecx, 1\n");
__asm("jz end_of_same_note_search\n"); //end of chart reached
__asm("cmp word ptr [eax+4], 0x245\n");
__asm("jne check_if_note_event\n"); //still need to check 0x145..
__asm("push ecx\n");
__asm("xor cx, cx\n");
__asm("mov cl, byte ptr [eax+7]\n");
__asm("shr ecx, 4\n"); //cl now contains button value ( 00-08 )
__asm("cmp cl, dl\n");
__asm("pop ecx");
__asm("jne check_if_note_event\n"); // 0x245 but not our button, still need to check 0x145..
__asm("jmp end_of_same_note_search\n"); // found matching 0x245 (keysound change for this button), we can stop search
__asm("check_if_note_event:\n");
__asm("cmp word ptr [eax+4], 0x145\n");
__asm("jne next_same_note_chunk\n");
__asm("cmp dl, byte ptr [eax+6]\n");
__asm("jne next_same_note_chunk\n");
//found a match! time to grow the chart..
__asm("push ecx\n");
__asm("push esi\n");
__asm("push edi\n");
__asm("mov esi, eax\n");
__asm("mov edi, eax\n");
__asm("add edi, 0x0C\n");
__asm("imul ecx, 0x0C\n"); //ecx is number of chunks left, we want number of bytes for now, dword later
__asm("std\n");
__asm("add esi, ecx\n");
__asm("sub esi, 0x04\n"); //must be on very last dword from chart
__asm("add edi, ecx\n");
__asm("sub edi, 0x04\n");
__asm("shr ecx, 2\n"); //div by 4 (sizeof dword)
__asm("rep movsd\n");
__asm("cld\n");
__asm("pop edi\n");
__asm("pop esi\n");
__asm("pop ecx\n");
/* write the 0x745 event copy */
// timestamp is already correct as it's leftover from the 0x145
__asm("mov dword ptr [eax+0x04], ebx\n"); // operation
__asm("mov dword ptr [eax+0x08], 0x0\n"); // cleanup possible longnote duration leftover
__asm("add esi, 1\n"); //increase growth counter
/* ebx still contains the 0x745 operation, dl still contains button, but eax points to 0x745 rather than the 0x145 so let's fix */
/* note that remaining chunks is still correct due to growth, so no need to decrement ecx */
__asm("add eax, 0xC\n");
__asm("jmp next_same_note_chunk\n"); //look for next occurrence
/* KEYSOUND PROCESS END */
__asm("end_of_same_note_search:\n");
/* restore before next timestamp */
__asm("pop ecx\n");
__asm("add ecx, esi\n"); // take chart growth into account
__asm("pop esi\n");
__asm("pop edx\n");
__asm("pop ebx\n");
__asm("pop eax\n");
/* next iteration should revisit the same block since we shifted... anticipate the +0xC/-1/+0x64 that will be done by real_chart_load() */
__asm("sub eax, 0xC\n");
__asm("add ecx, 1\n");
__asm("sub dword ptr [eax], 0x64\n");
__asm("jmp patch_chart_load_end\n");
__asm("end_of_chart:\n");
__asm("pop ecx\n");
__asm("pop esi\n");
__asm("pop edx\n");
__asm("pop ebx\n");
__asm("pop eax\n");
__asm("mov word ptr [eax+4], 0x245\n"); // no match found (ad-lib keysound?), restore opcode
__asm("patch_chart_load_end:\n");
real_chart_load();
}
static bool patch_disable_keysound()
{
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
{
int64_t pattern_offset = _search(data, dllSize, "\x00\x00\x72\x05\xB9\xFF", 6, 0);
if (pattern_offset == -1) {
LOG("popnhax: keysound disable: cannot find offset\n");
return false;
}
/* detect if usaneko+ */
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x0F;
uint8_t check_byte = *((uint8_t *)(patch_addr + 1));
if (check_byte == 0x04)
{
LOG("popnhax: keysound disable: old game version\n");
//return false;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)patch_chart_load_old,
(void **)&real_chart_load);
}
else
{
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)patch_chart_load,
(void **)&real_chart_load); //rewrite chart to get rid of keysounds
}
LOG("popnhax: no more keysounds\n");
}
return true;
}
static bool patch_keysound_offset(int8_t value)
{
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
g_keysound_offset = -1*value;
patch_add_to_base_offset(value);
{
int64_t pattern_offset = _search(data, dllSize, "\xC6\x44\x24\x0C\x00\xE8", 6, 0);
if (pattern_offset == -1) {
LOG("popnhax: keysound offset: cannot prepatch\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset - 0x07;
patch_memory(patch_addr, (char *)"\x03", 1); // change "mov esi" into "add esi"
_MH_CreateHook((LPVOID)(patch_addr-0x03), (LPVOID)patch_eval_timing,
(void **)&real_eval_timing); // preload esi with g_keysound_offset
if (!config.audio_offset)
LOG("popnhax: keysound offset: timing offset by %d ms\n", value);
else
LOG("popnhax: audio offset: audio offset by %d ms\n", -1*value);
}
return true;
}
static bool patch_add_to_beam_brightness(int8_t delta) {
int32_t new_value = delta;
char *as_hex = (char *) &new_value;
LOG("popnhax: beam brightness: adding %d to beam brightness.\n",delta);
uint32_t beam_brightness_addr;
if (!get_addr_beam_brightness(&beam_brightness_addr))
{
LOG("popnhax: beam brightness: cannot find base address\n");
return false;
}
int32_t current_value = *(int32_t *) beam_brightness_addr;
new_value = current_value+delta;
if (new_value < 0)
{
LOG("popnhax: beam brightness: fix invalid value (%d -> 0)\n",new_value);
new_value = 0;
}
if (new_value > 255)
{
LOG("popnhax: beam brightness: fix invalid value (%d -> 255)\n",new_value);
new_value = 255;
}
patch_memory(beam_brightness_addr, as_hex, 4);
patch_memory(beam_brightness_addr+0x39, as_hex, 4);
LOG("popnhax: beam brightness is now %d.\n",new_value);
return true;
}
static bool patch_beam_brightness(uint8_t value) {
uint32_t newvalue = value;
char *as_hex = (char *) &newvalue;
bool res = true;
/* call get_addr_beam_brightness() so that it can still work after base value is overwritten */
uint32_t beam_brightness_addr;
get_addr_beam_brightness(&beam_brightness_addr);
if (!find_and_patch_hex(g_game_dll_fn, "\xB8\x64\x00\x00\x00\xD9", 6, 0x3A, as_hex, 4))
{
LOG("popnhax: base offset: cannot patch HD beam brightness\n");
res = false;
}
if (!find_and_patch_hex(g_game_dll_fn, "\xB8\x64\x00\x00\x00\xD9", 6, 1, as_hex, 4))
{
LOG("popnhax: base offset: cannot patch SD beam brightness\n");
res = false;
}
return res;
}
uint16_t *g_course_id_ptr;
uint16_t *g_course_song_id_ptr;
void (*real_parse_ranking_info)();
void score_challenge_retrieve_addr()
{
/* only set pointer if g_course_id_ptr is not set yet,
* to avoid overwriting with past challenge data which
* is sent afterwards
*/
__asm("mov ebx, %0\n": :"b"(g_course_id_ptr));
__asm("test ebx, ebx\n");
__asm("jne parse_ranking_info\n");
__asm("lea %0, [esi]\n":"=S"(g_course_id_ptr): :);
__asm("lea %0, [esi+0x8D4]\n":"=S"(g_course_song_id_ptr): :);
__asm("parse_ranking_info:\n");
real_parse_ranking_info();
}
void (*score_challenge_prep_songdata)();
void (*score_challenge_song_inject)();
void (*score_challenge_retrieve_player_data)();
void (*score_challenge_is_logged_in)();
void (*score_challenge_test_if_normal_mode)();
void (*real_make_score_challenge_category)();
void make_score_challenge_category()
{
__asm("push ecx\n");
__asm("push ebx\n");
__asm("push edi\n");
if (g_course_id_ptr && *g_course_id_ptr != 0)
{
score_challenge_retrieve_player_data();
score_challenge_is_logged_in();
__asm("test al, al\n");
__asm("je leave_score_challenge\n");
score_challenge_test_if_normal_mode();
__asm("test al, al\n");
__asm("jne leave_score_challenge\n");
__asm("mov cx, %0\n": :"r"(*g_course_song_id_ptr));
__asm("mov dl,6\n");
__asm("lea eax,[esp+0x8]\n");
score_challenge_prep_songdata();
__asm("lea edi,[esi+0x24]\n");
__asm("mov ebx,eax\n");
score_challenge_song_inject();
__asm("mov byte ptr ds:[esi+0xA4],1\n");
}
__asm("leave_score_challenge:\n");
__asm("pop edi\n");
__asm("pop ebx\n");
__asm("pop ecx\n");
}
/* all code handling score challenge is still in the game but the
* function responsible for building and adding the score challenge
* category to song selection has been stubbed. let's rewrite it
*/
static bool patch_score_challenge()
{
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
/* Part1: retrieve course id and song id, useful and will simplify a little */
{
int64_t pattern_offset = _search(data, dllSize, "\x81\xC6\xCC\x08\x00\x00\xC7\x44\x24", 9, 0);
if (pattern_offset == -1) {
LOG("popnhax: score challenge: cannot find course/song address\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)score_challenge_retrieve_addr,
(void **)&real_parse_ranking_info);
}
/* Part2: retrieve subfunctions which used to be called by the now stubbed function */
{
int64_t pattern_offset = _search(data, dllSize, "\x66\x89\x08\x88\x50\x02", 6, 0);
if (pattern_offset == -1) {
LOG("popnhax: score challenge: cannot find song data prep function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
score_challenge_prep_songdata = (void(*)())patch_addr;
}
{
int64_t pattern_offset = _search(data, dllSize-0x60000, "\x8B\x4F\x0C\x83\xEC\x10\x56\x85\xC9\x75\x04\x33\xC0\xEB\x08\x8B\x47\x14\x2B\xC1\xC1\xF8\x02\x8B\x77\x10\x8B\xD6\x2B\xD1\xC1\xFA\x02\x3B\xD0\x73\x2B", 37, 0x60000);
if (pattern_offset == -1) {
LOG("popnhax: score challenge: cannot find category song inject function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
score_challenge_song_inject = (void(*)())patch_addr;
}
{
int64_t pattern_offset = _search(data, dllSize, "\x8B\x01\x8B\x50\x14\xFF\xE2\xC3\xCC\xCC\xCC\xCC", 12, 0);
if (pattern_offset == -1) {
LOG("popnhax: score challenge: cannot find check if logged function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x24;
score_challenge_retrieve_player_data = (void(*)())patch_addr;
}
{
int64_t pattern_offset = _search(data, dllSize, "\xE8\xDB\xFF\xFF\xFF\x33\xC9\x84\xC0\x0F\x94", 11, 0);
if (pattern_offset == -1) {
LOG("popnhax: score challenge: cannot find check if logged function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset - 0x20;
score_challenge_is_logged_in = (void(*)())patch_addr;
}
{
int64_t pattern_offset = _search(data, dllSize, "\xF7\xD8\x1B\xC0\x40\xC3\xE8", 7, 0);
if (pattern_offset == -1) {
LOG("popnhax: score challenge: cannot find check if normal mode function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x6;
score_challenge_test_if_normal_mode = (void(*)())patch_addr;
}
/* Part3: "unstub" the score challenge category creation */
{
int64_t pattern_offset = _search(data, dllSize, "\x83\xF8\x10\x77\x75\xFF\x24\x85", 8, 0);
if (pattern_offset == -1) {
pattern_offset = _search(data, dllSize, "\x83\xF8\x11\x77\x7C\xFF\x24\x85", 8, 0); // jam&fizz
if (pattern_offset == -1) {
LOG("popnhax: score challenge: cannot find category building loop\n");
return false;
}
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x66;
uint32_t function_offset = *((uint32_t*)(patch_addr+0x01));
uint64_t function_addr = patch_addr+5+function_offset;
_MH_CreateHook((LPVOID)(function_addr), (LPVOID)make_score_challenge_category,
(void **)&real_make_score_challenge_category);
}
LOG("popnhax: score challenge reactivated (requires server support)\n");
return true;
}
static bool patch_base_offset(int32_t value) {
char *as_hex = (char *) &value;
bool res = true;
/* call get_addr_timing_offset() so that it can still work after timing value is overwritten */
uint32_t original_timing;
get_addr_timing_offset(&original_timing);
uint32_t sd_timing_addr;
get_addr_sd_timing(&sd_timing_addr);
uint32_t hd_timing_addr;
get_addr_hd_timing(&hd_timing_addr);
if (!find_and_patch_hex(g_game_dll_fn, "\xB8\xC4\xFF\xFF\xFF", 5, 1, as_hex, 4))
{
LOG("popnhax: base offset: cannot patch base SD timing\n");
res = false;
}
if (!find_and_patch_hex(g_game_dll_fn, "\xB8\xB4\xFF\xFF\xFF", 5, 1, as_hex, 4))
{
LOG("popnhax: base offset: cannot patch base HD timing\n");
res = false;
}
return res;
}
static bool patch_hd_timing() {
if (!patch_base_offset(-76))
{
LOG("popnhax: HD timing: cannot set HD offset\n");
return false;
}
LOG("popnhax: HD timing forced\n");
return true;
}
static bool patch_hd_resolution(uint8_t mode) {
if (mode > 2)
{
LOG("ponhax: HD resolution invalid value %d\n",mode);
return false;
}
/* set popkun and beam brightness to 85 instead of 100, like HD mode does */
if (!patch_beam_brightness(85))
{
LOG("popnhax: HD resolution: cannot set beam brightness\n");
return false;
}
/* set window to 1360*768 */
if (!find_and_patch_hex(g_game_dll_fn, "\x0F\xB6\xC0\xF7\xD8\x1B\xC0\x25\xD0\x02", 10, -5, "\xB8\x50\x05\x00\x00\xC3\xCC\xCC\xCC", 9)
&& !find_and_patch_hex(g_game_dll_fn, "\x84\xc0\x74\x14\x0f\xb6\x05", 7, -5, "\xB8\x50\x05\x00\x00\xC3\xCC\xCC\xCC", 9))
{
LOG("popnhax: HD resolution: cannot find screen width function\n");
return false;
}
if (!find_and_patch_hex(g_game_dll_fn, "\x0f\xb6\xc0\xf7\xd8\x1b\xc0\x25\x20\x01", 10, -5, "\xB8\x00\x03\x00\x00\xC3\xCC\xCC\xCC", 9))
LOG("popnhax: HD resolution: cannot find screen height function\n");
if (!find_and_patch_hex(g_game_dll_fn, "\x8B\x54\x24\x20\x53\x51\x52\xEB\x0C", 9, -6, "\x90\x90", 2))
LOG("popnhax: HD resolution: cannot find screen aspect ratio function\n");
if ( mode == 1 )
{
/* move texts (by forcing HD behavior) */
if (!find_and_patch_hex(g_game_dll_fn, "\x1B\xC9\x83\xE1\x95\x81\xC1\x86", 8, -5, "\xB9\xFF\xFF\xFF\xFF\x90\x90", 7))
LOG("popnhax: HD resolution: cannot move gamecode position\n");
if (!find_and_patch_hex(g_game_dll_fn, "\x6a\x01\x6a\x00\x50\x8b\x06\x33\xff", 9, -7, "\xEB", 1))
LOG("popnhax: HD resolution: cannot move credit/network position\n");
}
LOG("popnhax: HD resolution forced%s\n",(mode==2)?" (centered texts)":"");
return true;
}
static bool patch_fps_uncap(uint16_t fps) {
if (fps != 0)
{
/* TODO: fix in spicetools and remove this */
uint8_t count = 0;
while (find_and_patch_hex(NULL, "\x55\x31\xC0\x89\xE5\x57\x8B\x4D\x08\x8D\x79\x04\xC7\x01\x00\x00\x00\x00\x83\xE7\xFC\xC7\x41\x24\x00\x00\x00\x00\x29\xF9\x83\xC1\x28\xC1\xE9\x02\xF3\xAB\x8B\x7D\xFC\xC9\xC3", 43, 0, "\x31\xC0\xC3", 3))
{
count++;
}
if (count)
{
LOG("popnhax: frame_limiter: patched %u instance(s) of memset(a1, 0, 40) (bad usbPadReadLast io hook)\n", count);
}
uint8_t ft = (1000 + (fps / 2)) / fps; // rounded 1000/fps
int8_t delta = 16-ft;
int8_t newval = -1*delta-2;
/* enforce fps rate */
if (!find_and_patch_hex(g_game_dll_fn, "\x7E\x07\xB9\x0C\x00\x00\x00\xEB\x09\x85\xC9", 11, -1, "\xFF", 1))
{
LOG("popnhax: frame_limiter: cannot patch frame limiter\n");
return false;
}
if (!find_and_patch_hex(g_game_dll_fn, "\x7E\x07\xB9\x0C\x00\x00\x00\xEB\x09\x85\xC9", 11, 2, "\x90\x90\x90\x90\x90", 5))
{
LOG("popnhax: frame_limiter: cannot patch frame limiter\n");
return false;
}
/* adjust sleep time (original code is "add -2", replace with "add newval") */
if (!find_and_patch_hex(g_game_dll_fn, "\x6A\x00\x83\xC1\xFE\x51\xFF", 7, 4, (char *)&newval, 1))
{
LOG("popnhax: frame_limiter: cannot patch frame limiter\n");
return false;
}
LOG("popnhax: fps capped to %u fps (%ums frame time, new val %d)\n", fps, ft, newval);
return true;
}
if (!find_and_patch_hex(g_game_dll_fn, "\x7E\x07\xB9\x0C\x00\x00\x00\xEB\x09\x85\xC9", 11, 0, "\xEB\x1C", 2))
{
LOG("popnhax: fps uncap: cannot find frame limiter\n");
return false;
}
LOG("popnhax: fps uncapped\n");
return true;
}
uint32_t g_autopin_offset = 0;
uint32_t g_pincode;
uint32_t g_last_enter_pincode;
void (*real_pincode)(void);
void hook_pincode()
{
__asm("push eax\n");
__asm("push ecx\n");
__asm("push edx\n");
__asm("call _timeGetTime@0\n");
__asm("sub eax, [_g_last_enter_pincode]\n");
__asm("cmp eax, 30000\n"); //30sec cooldown (will only try once)
__asm("pop edx\n");
__asm("pop ecx\n");
__asm("ja enter_pincode\n"); //skip entering pin if cooldown value not reached
__asm("pop eax\n");
__asm("jmp call_real_pincode_handling\n");
__asm("enter_pincode:\n");
__asm("add [_g_last_enter_pincode], eax"); // place curr_time in g_last_enter_pincode (cancel sub eax, [_g_last_playsram])
__asm("pop eax\n");
__asm("push eax\n");
__asm("mov eax, dword ptr [_g_pincode]\n");
__asm("mov byte ptr [esi], 4\n");
__asm("add esi, 0x30\n");
__asm("mov [esi], eax\n");
__asm("sub esi, 0x30\n");
__asm("cmp dword ptr [_g_autopin_offset], 0\n");
__asm("je skip_2_crash\n");
__asm("mov eax, dword ptr [_g_autopin_offset]\n");
__asm("add eax, edi\n");
__asm("mov byte ptr [eax], 2\n");
__asm("add eax, 4\n");
__asm("mov byte ptr [eax], 4\n");
__asm("skip_2_crash:\n");
__asm("pop eax\n");
__asm("call_real_pincode_handling:\n");
real_pincode();
}
static bool patch_autopin()
{
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
FILE *file = fopen("_pincode.secret", "r");
char line[32];
if(file == NULL)
{
LOG("popnhax: autopin: cannot open '_pincode.secret' file\n");
return false;
}
if (!fgets(line, sizeof(line), file)){
LOG("popnhax: autopin: cannot read '_pincode.secret' file\n");
fclose(file);
return false;
}
fclose(file);
memcpy(&g_pincode, line, 4);
/* retrieve offset where to put values to prevent a crash */
{
int64_t pattern_offset = _search(data, dllSize, "\x1C\x10\xC7\x87", 4, 0);
if (pattern_offset == -1) {
LOG("WARNING: autopin: cannot find pincode special offset, might crash\n");
}
g_autopin_offset = *(uint32_t *)((int64_t)data + pattern_offset + 4);
}
/* auto enter pincode */
{
int64_t pattern_offset = _search(data, dllSize, "\x33\xC4\x89\x44\x24\x14\xA1", 7, 0);
if (pattern_offset == -1) {
LOG("popnhax: autopin: cannot find pincode handling function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_pincode,
(void **)&real_pincode);
}
LOG("popnhax: autopin enabled\n");
return true;
}
void (*real_song_options)();
void patch_song_options() {
__asm("mov byte ptr[ecx+0xA15], 0\n");
real_song_options();
}
void (*real_numpad0_options)();
void patch_numpad0_options() {
__asm("mov byte ptr[ebp+0xA15], 1\n");
real_numpad0_options();
}
/* ----------- r2nk226 ----------- */
/* popn23 or less Check */
static bool version_check() {
/* get_version() is available, so use it */
uint32_t *g_chartbase_addr;
int32_t shift = -9;
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
/* prepare for g_chartbase_addr */
int64_t pre_gchartaddr = _search(data, dllSize, "\x8A\xC8\xBA", 3, 0);
if (pre_gchartaddr == -1) {
LOG("popnhax: chart_baseaddr was not found\n");
return false;
}
/* po21-po23 */
if (config.game_version == 0) {
uint32_t* check_E8 = (uint32_t *)((int64_t)data + pre_gchartaddr -5);
uint8_t val = *check_E8;
if (val == 0xE8) {
LOG("popnhax: pop'n 21 sunny park\n");
config.game_version = 21;
shift = -9;
} else {
int64_t pattern_offset = _search(data, dllSize, "\x83\xF8\x03\x77\x6B", 5, 0);
if (pattern_offset == -1) {
LOG("popnhax: pop'n 23 eclale\n");
config.game_version = 23;
} else {
LOG("popnhax: pop'n 22 lapistoria\n");
config.game_version = 22;
}
}
}
g_chartbase_addr = (uint32_t *)((int64_t)data + pre_gchartaddr + shift);
chartbase_addr = *g_chartbase_addr;
#if DEBUG == 1
LOG("debug : chartbase_addr is 0x%X\n", chartbase_addr);
#endif
return true;
}
const char *popkun_change = "gmcmh.ifs";
void (*gm_ifs_load)();
void loadtexhook() {
if(p_record) {
__asm("cmp dword ptr [eax+0x0B], 0x2E6E6D63\n"); // gm 'cmn.' ifs
__asm("jne change_skip\n");
__asm("mov %0, eax\n"::"a"(popkun_change));
__asm("change_skip:\n");
}
gm_ifs_load();
}
char *ifsname_ptr = NULL;
void (*get_ifs_name)();
void get_ifs_filename() {
__asm("mov %0, eax\n":"=a"(ifsname_ptr): :);
get_ifs_name();
}
uint32_t elapsed_time = 0;
void (*get_elapsed_time_hook)();
void get_elapsed_time() {
__asm("mov %0, eax\n":"=a"(elapsed_time): :);
get_elapsed_time_hook();
}
void flag_reset() {
hide_menu = true;
sp_reset = false;
**usbpad_addr = 1;
speed = 5;
new_speed = 100;
mul_2dx = 1.;
r_ran = false;
regul_flag = false;
is_resultscreen_flag = false;
disp = false;
use_sp_flag = false;
stop_input = false;
stop_recchange = true;
p_record = false;
find_recdata = false;
rec_reload = false;
recsavefile = false;
if (recbinArray_loaded != NULL) {
free(recbinArray_loaded);
recbinArray_loaded = NULL;
#if DEBUG == 1
LOG("popnhax: load data memory free\n");
#endif
}
if (recbinArray_writing != NULL) {
free(recbinArray_writing);
recbinArray_writing = NULL;
#if DEBUG == 1
LOG("popnhax: recording data memory free\n");
#endif
}
}
void check_recdatafile(uint32_t rec_elements) {
uint32_t check_notes = LOWORD(recbinArray_loaded[0].timestamp);
uint32_t check_cool = 0;
uint32_t check_great = 0;
uint32_t check_good = 0;
uint32_t check_bad = 0;
uint32_t val = 0;
uint16_t c_err = 0;
/* first check : file size */
if (rec_elements -2 != check_notes) {
c_err |= 20;
LOG("popnhax: record: File size is different\n");
}
/* second check : judge count */
for (uint32_t i=0; i < check_notes; i++) {
if (recbinArray_loaded[i+2].judge == 2) {
check_cool++;
} else if ((recbinArray_loaded[i+2].judge == 3) || (recbinArray_loaded[i+2].judge == 4)) {
check_great++;
} else if ((recbinArray_loaded[i+2].judge == 5) || (recbinArray_loaded[i+2].judge == 6)) {
check_good++;
} else if ((recbinArray_loaded[i+2].judge == 1) || (recbinArray_loaded[i+2].judge == 7) || (recbinArray_loaded[i+2].judge == 8) || (recbinArray_loaded[i+2].judge == 9)) {
check_bad++;
}
}
uint32_t cg = (check_great << 16) | check_cool;
uint32_t gb = (check_bad << 16) | check_good;
val = recbinArray_loaded[0].judge | (recbinArray_loaded[0].button >> 8) | (recbinArray_loaded[0].flag >> 16) | (recbinArray_loaded[0].pad[0] >> 24);
val = val ^ cg ^ gb ^ 0x672DE ^ recbinArray_loaded[0].timestamp;
if (recbinArray_loaded[0].recid != cg) {
c_err |= 1;
}
if ((uint32_t)recbinArray_loaded[0].timing != gb) {
c_err |= 2;
}
if (recbinArray_loaded[1].button != (uint8_t)~recbinArray_loaded[0].button) {
c_err |= 4;
}
if (recbinArray_loaded[1].flag != (uint8_t)~recbinArray_loaded[0].flag) {
c_err |= 8;
}
if (val == 0) {
c_err |= 0x10;
}
#if DEBUG == 0
if (c_err == 0) {
//find_recdata = true;
stop_recchange = false; // recmode_select enabled
/* ok chime */
__asm("mov eax, 0x20\n");
__asm("push 0\n");
__asm("call %0\n"::"D"(playsramsound_func));
__asm("add esp, 4\n");
/* Initialize header */
uint32_t start_addr = (uint32_t)&recbinArray_loaded[2].timestamp;
uint32_t *play_ptr = (uint32_t*)&recbinArray_loaded[0].judge;
*play_ptr = start_addr;
recbinArray_loaded[0].recid = chartbase_addr;
recbinArray_loaded[0].timing = 0;
} else {
find_recdata = false;
stop_recchange = true;
LOG("popnhax: This file has been modified (%X)\n", c_err);
}
#else
/* debug : force rec_select enabled */
uint32_t judge_1 = 0;
uint32_t judge_7 = 0;
uint32_t judge_8 = 0;
uint32_t judge_9 = 0;
uint32_t judge_B = 0;
uint32_t judge_C = 0;
for (uint32_t i=0; i < check_notes; i++) {
if (recbinArray_loaded[i+2].judge == 1) {
judge_1++;
} else if (recbinArray_loaded[i+2].judge == 7) {
judge_7++;
} else if (recbinArray_loaded[i+2].judge == 8) {
judge_8++;
} else if (recbinArray_loaded[i+2].judge == 9) {
judge_9++;
} else if (recbinArray_loaded[i+2].judge == 0x0B) {
judge_B++;
} else if (recbinArray_loaded[i+2].judge == 0x0C) {
judge_C++;
}
}
printf("popnhax: recbin: cool(%d), great(%d), good(%d), bad(%d)\n",
check_cool, check_great, check_good, check_bad);
printf("popnhax: recbin: cg is %08X. gb is %08X. val is %08X.\n", cg, gb, val);
printf("popnhax: recbin: c_err val is (%X)\n", c_err);
printf("popnhax: recbin: judge_1(%d), judge_7(%d), judge_8(%d), judge_9(%d), judge_B(%d), judge_C(%d)\n",
judge_1, judge_7, judge_8, judge_9, judge_B, judge_C);
printf("popnhax: recbinArray_loaded addr is 0x%p\n", recbinArray_loaded);
//find_recdata = true;
stop_recchange = false;
/* ok chime */
__asm("mov eax, 0x20\n");
__asm("push 0\n");
__asm("call %0\n"::"D"(playsramsound_func));
__asm("add esp, 4\n");
/* Initialize header */
uint32_t start_addr = (uint32_t)&recbinArray_loaded[2].timestamp;
uint32_t *play_ptr = (uint32_t*)&recbinArray_loaded[0].judge;
*play_ptr = start_addr;
recbinArray_loaded[0].recid = chartbase_addr;
recbinArray_loaded[0].timing = 0;
#endif
}
bool recording_memoryset() {
uint32_t size = sizeof(struct REC) * 0xC80;
if (recbinArray_writing == NULL) {
recbinArray_writing = (struct REC*)malloc(size);
if (recbinArray_writing == NULL) {
return false;
}
}
memset(recbinArray_writing, 0, size);
/* Initialize header */
uint32_t start_addr = (uint32_t)&recbinArray_writing[2].timestamp;
uint32_t *play_ptr = (uint32_t*)&recbinArray_writing[0].judge;
*play_ptr = start_addr;
recbinArray_writing[0].recid = chartbase_addr;
recbinArray_writing[0].timing = 0;
#if DEBUG == 1
LOG("popnhax: record: 50 kilobytes reserved for recording\n");
LOG("popnhax: recbinArray_writing addr is 0x%p\n", recbinArray_writing);
#endif
return true;
}
void recid2str(char* mrecid) {
const char *m_diff[8] = {"ep", "np", "hp", "op", "NN", "NH", "HN", "HH"};
uint16_t m_idx = HIWORD(rec_musicid);
uint16_t m_id = LOWORD(rec_musicid);
uint8_t pattern = 0; // default (ep)
switch (m_idx) {
case 0x0101:
pattern = 1;
break;
case 0x0202:
pattern = 2;
break;
case 0x0303:
pattern = 3;
break;
case 0x0404:
pattern = 4;
break;
case 0x0405:
pattern = 5;
break;
case 0x0504:
pattern = 6;
break;
case 0x0505:
pattern = 7;
break;
}
snprintf(mrecid, 10, "%04d(%s)", m_id, m_diff[pattern]);
LOG("popnhax: m_id(%04d), m_idx(%04X), mrecid(%s)\n", m_id, m_idx, mrecid);
}
static bool record_playdata_start() {
const char *filename = NULL;
SearchFile s;
char recid[10];
find_recdata = false;
get_recPlayoptions();
recid2str(recid);
char currentDirectory[FILENAME_MAX];
if (_getcwd(currentDirectory, sizeof(currentDirectory)) == NULL) {
LOG("popnhax: currentDirectory not found\n");
return false;
}
char folderPath[FILENAME_MAX];
snprintf(folderPath, sizeof(folderPath), "%s\\rec", currentDirectory);
struct stat st;
if (stat(folderPath, &st) != 0) {
mkdir(folderPath);
LOG("popnhax: make dir (rec)\n");
}
s.search("rec", "bin", false);
auto result = s.getResult();
for (uint16_t i = 0; i < result.size(); i++) {
filename = result[i].c_str() +4;
if (strstr(result[i].c_str(), (char*)recid) != NULL) {
LOG("popnhax: record: found matching recmusicid, end search\n");
LOG("popnhax: record: filename = %s\n", filename);
find_recdata = true;
break;
}
}
/* Data is found and loaded into recbinArray_loaded */
if (find_recdata) {
FILE *recbin;
uint32_t size;
uint32_t rec_elements;
char filePath[FILENAME_MAX];
snprintf(filePath, sizeof(filePath), "%s\\rec\\%s", currentDirectory, filename);
recbin = fopen(filePath, "rb");
if (recbin == NULL) {
LOG("popnhax: record: filePath is %s\n", filePath);
LOG("popnhax: record: cannot open %s\n", filename);
return false;
}
fseek(recbin, 0, SEEK_END);
size = ftell(recbin);
fseek(recbin, 0, SEEK_SET);
rec_elements = size / sizeof(struct REC);
recbinArray_loaded = (struct REC*)malloc(size);
if (recbinArray_loaded == NULL) {
LOG("popnhax: record: memory allocation failure\n");
return false;
}
memset(recbinArray_loaded, 0, size);
fread(recbinArray_loaded, sizeof(struct REC), rec_elements, recbin);
fclose(recbin);
/* Checking loaded file */
check_recdatafile(rec_elements);
} else {
// find_recdata = false
LOG("popnhax: record: matching recmusicid not found\n");
}
return true;
}
void (*real_musicselect)();
void hook_musicselect() {
uint32_t ecxsafe;
__asm("mov %0, ecx\n":"=c"(ecxsafe): :);
flag_reset();
__asm("mov ecx, %0\n"::"c"(ecxsafe));
real_musicselect();
}
void rec_id_check() {
__asm("push ecx\n");
__asm("push ebp\n");
__asm("mov eax, dword ptr [%0]\n"::"a"(p_note));
__asm("mov esi, [eax]\n");
__asm("mov eax, [%0]\n"::"a"(&recbinArray_loaded));
__asm("mov ebp, eax\n");
__asm("xor al, al\n");
__asm("mov ecx, dword ptr [esi+0x04]\n");
__asm("sub ecx, dword ptr [ebp+0x0C]\n");
__asm("mov ebp, dword ptr [ebp+0x04]\n");
__asm("cmp word ptr [ebp+0x0C], cx\n");
__asm("jne rec_id_nomatch\n");
// redID matched
__asm("movzx ecx, byte ptr [ebp+0x04]\n"); // judge
__asm("cmp dword ptr [esp+0x08+0x04], 0x01\n"); // bad_check flag
__asm("jne long_check\n"); // Go long check without BAD check
__asm("cmp ecx, 0x07\n"); // bad_check 7 - B
__asm("jl long_check\n"); // Not BAD, so go to long check (al=1)
__asm("xor ebx, ebx\n");
__asm("or word ptr [esi+0x18], 0x02\n");
__asm("cmp ecx, 0x09\n");
__asm("jne long_check\n");
__asm("mov ebx, 0x02\n");
__asm("xor ecx, ecx\n");
__asm("long_check:\n");
__asm("mov eax, dword ptr [esi+0x04]\n");
__asm("cmp dword ptr [eax+0x08], 0\n"); // long check
__asm("jbe id_match_end\n");
__asm("long_start:\n");
__asm("cmp ecx, 0x07\n"); // bad_check 7 - B
__asm("ja id_match_end\n");
__asm("mov byte ptr [esi+0x60], cl\n"); // save long_judge
__asm("id_match_end:\n");
__asm("cmp cl, 0\n");
__asm("setne al\n"); // Finish with al=0 only when slow BAD
__asm("rec_id_nomatch:\n");
__asm("pop ebp\n");
__asm("pop ecx\n");
}
void call_guidese() {
uint8_t *g_guidese_addr;
uint8_t stage_no = 0;
uint32_t option_offset = 0;
stage_no = *(uint8_t*)(**player_options_addr +0x0E);
option_offset = player_option_offset * stage_no;
g_guidese_addr = (uint8_t*)(**player_options_addr +0x37 +option_offset);
__asm("movzx ecx, %0\n"::"a"(*g_guidese_addr));
__asm("cmp cl, 0\n");
__asm("je call_end\n");
__asm("mov eax, dword ptr [esp+0x08]\n"); // judge+button
__asm("and eax, 0x00FF\n"); // judge only
__asm("cmp al, 0x02\n");
__asm("jne se_continue\n");
// COOL
__asm("push 0\n");
__asm("jmp se_call\n");
__asm("se_continue:\n");
__asm("cmp al, 0x04\n");
__asm("je GOOD\n");
__asm("cmp al, 0x03\n");
__asm("je GOOD\n");
__asm("cmp al, 0x06\n");
__asm("je GREAT\n");
__asm("cmp al, 0x05\n");
__asm("je GREAT\n");
__asm("jmp call_end\n");
__asm("GREAT:\n");
__asm("push 0x13880\n");
__asm("jmp se_call\n");
__asm("GOOD:\n");
__asm("push 0x0EA60\n");
__asm("se_call:\n");
if (config.game_version >= 27) {
__asm("dec ecx\n");
}
__asm("mov eax, 0x21\n");
__asm("add eax, ecx\n");
__asm("call %0\n"::"D"(playsramsound_func));
__asm("add esp, 4\n");
__asm("call_end:\n");
}
void (*hook_playfirst)();
void play_firststep() {
if (!demo_flag && p_record) {
__asm("push eax\n");
__asm("push ebx\n");
__asm("push ecx\n");
__asm("mov eax, [%0]\n"::"a"(&recbinArray_loaded));
__asm("mov ebx, dword ptr [eax+0x04]\n"); // play_ptr
__asm("cmp edi, 0\n"); // edi = elapsed time
__asm("jne p1_start\n");
__asm("p1_end:\n");
__asm("xor eax, eax\n");
__asm("jmp p1_endmerge\n");
__asm("p1_start:\n");
__asm("movzx ecx, word ptr [eax+0x08]\n"); // play counter
__asm("cmp word ptr [eax], cx\n"); // [eax] = record counter
__asm("jle p1_end\n");
#if DEBUG == 1
/* debug: skip test */
__asm("cmp word ptr [ebx+0x06], 0x0101\n");
__asm("je countup\n");
#endif
/* E2flag can sometimes produce fastGOOD after the long disappears (perhaps a bug?).
It's hard to reproduce, so we do a temporary process.
If it is not played by normal processing, call judge_bar_func */
__asm("cmp byte ptr [ebx+0x06], 0xE2\n");
__asm("jne skip_check\n");
__asm("cmp edi, dword ptr [eax]\n");
__asm("jl p1_end\n"); // elapsed time < Timing -> return
__asm("movzx ecx, word ptr [ebx+0x04]\n"); // Judgment should only be 5 (need to check?)
__asm("push eax\n");
__asm("push ecx\n");
call_guidese();
__asm("add esp, 4\n");
__asm("pop eax\n");
__asm("mov ebx, dword ptr [eax+0x04]\n");
__asm("movzx eax, word ptr [ebx+0x04]\n"); // judge+button
__asm("push eax\n"); // send judge
__asm("call %0\n"::"a"(judge_bar_func)); // eax, ecx, edx used
__asm("pop eax\n");
__asm("countup:\n");
__asm("inc word ptr [eax+0x08]\n"); // play counter
__asm("add dword ptr [eax+0x04], 0x10\n"); // next_ptr
__asm("jmp p1_end\n");
__asm("skip_check:\n");
__asm("movzx ecx, byte ptr [ebx+0x04]\n");
__asm("cmp cl, 0x01\n"); // judge=1 (poor)
__asm("jle countup\n"); // 0.1 -> skip
__asm("cmp cl, 0x0A\n"); // A.B~ -> skip_test
__asm("jge countup\n");
__asm("p1_idcheck:\n"); // judge 2 - 9
__asm("mov ecx, eax\n");
__asm("push 0\n"); // id-checkOnly
rec_id_check();
__asm("add esp, 4\n");
__asm("test al, al\n");
__asm("je p1_end\n");
// id matched
__asm("mov eax, dword ptr [ecx+0x04]\n");
__asm("mov eax, dword ptr [eax+0x08]\n"); // inputTiming
__asm("neg eax\n");
__asm("p1_endmerge:\n");
__asm("pop ecx\n");
__asm("pop ebx\n");
__asm("add ecx, eax\n");
__asm("pop eax\n");
}
hook_playfirst();
}
void (*last_auto_flag_check)();
void play_secondstep() {
if (demo_flag) {
__asm("mov al, 0x01\n");
} else if (!demo_flag) {
if (p_record) {
__asm("push edx\n");
__asm("cmp ebx, 0x02\n"); // long_end_flag
__asm("jne p2_start\n");
/* long_end flow */
__asm("movzx eax, byte ptr [esi+0x12]\n"); // button
__asm("movzx ecx, byte ptr [esi+0x60]\n"); // saved_judge
__asm("shl ax, 8\n");
__asm("or eax, ecx\n");
__asm("push eax\n"); // send judge
__asm("call %0\n"::"a"(judge_bar_func));
__asm("add esp, 4\n");
__asm("mov ebx, 0x01\n");
__asm("jmp p2_end\n"); // no call guideSE
__asm("p2_start:\n");
__asm("mov edx, [%0]\n"::"d"(&recbinArray_loaded));
__asm("mov eax, dword ptr [edx+0x04]\n");
__asm("inc word ptr [edx+0x08]\n"); // play counter
__asm("add dword ptr [edx+0x04], 0x10\n"); // next_ptr
__asm("movzx eax, word ptr [eax+0x04]\n"); // judge+button
__asm("push eax\n"); // send judge
__asm("call %0\n"::"a"(judge_bar_func)); // eax,ecx,edx used
__asm("pop eax\n");
__asm("push eax\n"); // send judge
call_guidese();
__asm("add esp, 4\n");
__asm("p2_end:\n");
__asm("mov eax, [%0]\n"::"a"(p_note));
__asm("mov esi, [eax]\n");
__asm("mov ecx, esi\n"); // p_note
__asm("pop edx\n");
__asm("xor al, al\n");
}
}
last_auto_flag_check();
}
void (*first_auto_flag_check)();
void play_thirdstep() {
__asm("push edx\n");
if (demo_flag) {
__asm("mov al, 1\n"); // for DEMO play
} else if (!demo_flag) {
if (p_record) {
/* playback record */
__asm("mov edx, [%0]\n"::"d"(&recbinArray_loaded));
__asm("mov ebx, [esi+0x04]\n"); // real chart_addr
__asm("cmp dword ptr [ebx+0x08], 0\n"); // long check
__asm("mov ebx, 0x01\n");
__asm("jbe id_check\n");
__asm("movzx eax, word ptr [esi+0x18]\n"); // flag
__asm("test al, 0x80\n"); // long_end check
__asm("jne p3_long_end\n");
__asm("id_check:\n");
__asm("push 1\n"); // recID & bad_check
rec_id_check();
__asm("add esp, 4\n");
__asm("cmp ebx, 2\n");
__asm("jne p3_return\n");
__asm("or word ptr [esi+0x18], 0x02\n"); // bad_flag
__asm("mov ebx, dword ptr [edx+0x04]\n"); // play_ptr
__asm("inc word ptr [edx+0x08]\n");
__asm("add dword ptr [edx+0x04], 0x10\n"); // next_ptr
__asm("movzx ebx, word ptr [ebx+0x04]\n"); // judge+button
__asm("push ebx\n");
__asm("call %0\n"::"a"(judge_bar_func));
__asm("add esp, 4\n");
__asm("xor al, al\n");
__asm("mov ebx, 0x02\n");
__asm("jmp p3_return\n");
__asm("p3_long_end:\n");
__asm("push 0\n");
rec_id_check();
__asm("add esp, 4\n");
__asm("cmp al, bl\n");
__asm("jne p3_end\n");
__asm("inc word ptr [edx+0x08]\n"); // play counter
__asm("add dword ptr [edx+0x04], 0x10\n"); // next_ptr
__asm("p3_end:\n");
__asm("mov eax, ebx\n");
} else if (!p_record) {
__asm("xor al, al\n"); // recording
}
}
__asm("p3_return:\n");
__asm("pop edx\n");
first_auto_flag_check();
}
void (*long_end_flow)();
void play_fourthstep() {
// movzx dx, byte ptr [esi+0x12] // button
// movzx ecx, byte ptr [esi+0x60] // 0 (for long_bar clear)
// shl dx, 8 // 0xYY00 : YY = buttonNo.
__asm("mov ebx, 0x02\n"); // long_end_flag. play_secondstep will catch it
__asm("xor ecx, ecx\n"); // saved judge clear
__asm("p4_continue:\n");
if (demo_flag) {
__asm("dec ebx\n");
}
long_end_flow();
}
void (*get_judge)();
void record_playdata() {
if (!p_record && stop_recchange) {
// AX = judge+button
__asm("mov ebx, %0\n"::"b"(elapsed_time));
__asm("test ebx, ebx\n");
__asm("je rec1_skip\n");
__asm("push ecx\n");
__asm("push edx\n");
__asm("mov ecx, dword ptr [%0]\n"::"c"(&recbinArray_writing));
__asm("mov edx, dword ptr [ecx+0x04]\n"); // rec_ptr
__asm("cmp al, 0x0B\n");
__asm("je rec1_end\n");
// __asm("cmp esi, 0\n");
// __asm("jne rec1_end\n");
__asm("rec1_countup:\n");
__asm("inc word ptr [ecx]\n"); // rec_counter
__asm("add dword ptr [ecx+0x04], 0x10"); // next_ptr
__asm("mov dword ptr [edx], ebx\n"); // elapsed time
__asm("mov word ptr [edx+0x04], ax\n"); // judge+button
__asm("mov ebx, dword ptr [%0]\n"::"b"(j_win_addr));
__asm("shr eax, 8\n"); // button
__asm("mov ebx, dword ptr [ebx+eax*8+0x04]\n");
__asm("mov dword ptr [edx+0x08], ebx\n"); // input timing
__asm("test esi, esi\n"); // [esi]=p_note
__asm("jne rec1_recid\n");
__asm("mov dword ptr [edx+0x08], esi\n"); // p_note Ȃ ^ C ~ O
__asm("mov dword ptr [edx+0x0C], esi\n"); // recid 0 ɂ
__asm("jmp rec1_end\n");
__asm("rec1_recid:\n");
__asm("mov eax, dword ptr [esi+0x04]\n"); // [p_note + 4] -> chart_addr
__asm("sub eax, dword ptr [ecx+0x0C]\n"); // chart_addr - baseaddr -> recID
__asm("mov dword ptr [edx+0x0C], eax\n"); // recID
__asm("movzx eax, byte ptr [esi+0x18]\n"); // flags
__asm("mov byte ptr [edx+0x06], al\n");
__asm("test al, 0x80\n"); // long_flag
__asm("je rec1_end\n");
__asm("test al, 0x02\n");
__asm("je rec1_end\n");
__asm("mov byte ptr [edx+0x07], 0xFF\n"); // long&Bad flag
__asm("rec1_end:\n");
__asm("movzx eax, word ptr [edx+0x04]\n"); // judge+button -> AX
__asm("pop edx\n");
__asm("pop ecx\n");
__asm("rec1_skip:\n");
}
get_judge();
}
void (*get_poor)();
void record_playdata_poor() {
// dx = (poor)judge+button
if (!p_record && stop_recchange && !demo_flag) {
// (demo_flag = true) -> demo screen
__asm("push edx\n");
__asm("mov eax, dword ptr [%0]\n"::"a"(&recbinArray_writing));
__asm("mov ecx, dword ptr [eax+0x04]\n"); // rec_ptr
__asm("mov word ptr [ecx+0x04], dx\n"); // poor_judge+button
__asm("mov word ptr [ecx+0x06], 0x0101\n"); // skip_test flag
__asm("mov edx, dword ptr [esi+0x04]\n"); // p_note+4 -> chart_addr
__asm("sub edx, dword ptr [eax+0x0C]\n"); // chart_addr - baseaddr -> recID
__asm("mov dword ptr [ecx+0x0C], edx\n"); // recID
__asm("inc word ptr [eax]\n"); // rec counter
__asm("add dword ptr [eax+0x04], 0x10\n"); // next_ptr
__asm("mov dword ptr [ecx], %0\n"::"a"(elapsed_time)); // elapsed time
__asm("pop edx\n");
}
get_poor();
}
int compare(const void* a, const void* b) {
struct REC* elementA = (struct REC*)a;
struct REC* elementB = (struct REC*)b;
if (elementA->timestamp != elementB->timestamp) {
return elementA->timestamp - elementB->timestamp;
} else {
return elementB->recid - elementA->recid;
}
}
void (*pre_judge_time)();
void record_timing_override() {
__asm("mov dword ptr [eax+4], esi\n");
pre_judge_time();
}
void (*flag_status)();
void reset_2dx_magnification_setting() {
mul_2dx = 1.;
__asm("xor eax, eax\n");
flag_status();
}
void load_recPlayoptions() {
uint16_t temp = (uint16_t)(recbinArray_loaded[0].timestamp >> 16);
uint8_t rec_cmp = temp & 0x0F;
if (speed != rec_cmp) {
rec_reload = true;
stop_recchange = true;
g_return_to_options = true;
speed = rec_cmp;
new_speed = 150 - (speed * 10);
}
rec_cmp = (temp >> 4) & 0x0F;
if (regul_flag != (uint8_t)rec_cmp) {
rec_reload = true;
stop_recchange = true;
g_return_to_options = true;
regul_flag = (uint8_t)rec_cmp;
}
mul_2dx = (double)(((double)new_speed)/100.);
uint32_t *g_options_addr;
uint8_t *g_rechispeed_addr;
uint8_t stage_no = 0;
uint32_t option_offset = 0;
stage_no = *(uint8_t*)(**player_options_addr +0x0E);
option_offset = player_option_offset * stage_no;
g_options_addr = (uint32_t*)(**player_options_addr +0x34 +option_offset);
*g_options_addr = (uint32_t)recbinArray_loaded[1].timestamp;
rec_options = *g_options_addr;
if (config.game_version == 24) {
if (HIBYTE(rec_options) != 0) {
rec_options &= 0x00FFFFFF; // GuideSEon 01~03 -> 00 (usaneko on)
} else {
rec_options |= 0x01000000; // GuideSEoff 00 -> 01 (usaneko off)
}
}
g_rechispeed_addr = (uint8_t*)(**player_options_addr +0x2A +option_offset);
*g_rechispeed_addr = recbinArray_loaded[1].judge;
}
bool recdata_save() {
if (!p_record) {
// (p_record) 0,record 1,playback
const char *filename = NULL;
SearchFile s;
char recid[10];
char recdate[15];
uint32_t size = LOWORD(recbinArray_writing[0].timestamp);
qsort(recbinArray_writing +2, size, sizeof(struct REC), compare);
recid2str(recid);
char currentDirectory[FILENAME_MAX];
if (_getcwd(currentDirectory, sizeof(currentDirectory)) == NULL) {
LOG("popnhax: currentDirectory not found\n");
return false;
}
char folderPath[FILENAME_MAX];
snprintf(folderPath, sizeof(folderPath), "%s\\rec", currentDirectory);
struct stat st;
if (stat(folderPath, &st) != 0) {
mkdir(folderPath);
LOG("popnhax: make directory (rec)\n");
}
__asm("mov esi, %0\n"::"S"(recdate));
__asm("call %0\n"::"a"(date_func));
filename = (char*)recid;
char filePath[FILENAME_MAX];
snprintf(filePath, sizeof(filePath), "%s\\rec\\%s-%s_%s.bin",
currentDirectory, filename, recdate, ifsname_ptr);
FILE* output_file = fopen(filePath, "wb");
if (output_file == NULL) {
LOG("popnhax: output_file create error\n");
return false;
}
fwrite(recbinArray_writing, sizeof(struct REC), size +2 , output_file);
fclose(output_file);
LOG("popnhax: recording data save done\n");
LOG("popnhax: savefilePath is %s\n", filePath);
recsavefile = true;
/* ok voice */
__asm("mov eax, 0x16\n");
__asm("push 0\n");
__asm("call %0\n"::"D"(playsramsound_func));
__asm("add esp, 4\n");
#if DEBUG == 1
LOG("popnhax: recid =%s\n", recid);
LOG("popnhax: recdate =%s\n", recdate);
#endif
return true;
}
return false;
}
void prepare_for_play_record() {
stop_recchange = true;
get_recPlayoptions();
if (p_record) {
if (recbinArray_loaded == NULL) {
LOG("popnhax: record: recbin load error (2)\n");
p_record = false;
} else {
r_ran = false;
use_sp_flag = true;
load_recPlayoptions();
**usbpad_addr = 0;
}
} else if (!p_record) {
if(!recording_memoryset()) {
LOG("popnhax: record: memory allocation failure\n");
}
}
if (regul_flag || r_ran || p_record) {
use_sp_flag = true;
}
disp = true;
hide_menu = true;
}
void (*real_optionloop_after_pressing_red)();
void hook_optionloop_after_pressing_red() {
__asm("push ebp\n");
__asm("push ebx\n");
__asm("push edx\n");
prepare_for_play_record();
__asm("pop edx\n");
__asm("pop ebx\n");
__asm("pop ebp\n");
real_optionloop_after_pressing_red();
}
void (*real_optionloop_after_pressing_yellow)();
void hook_optionloop_after_pressing_yellow() {
__asm("push ebp\n");
__asm("push ebx\n");
__asm("push edx\n");
prepare_for_play_record();
__asm("pop edx\n");
__asm("pop ebx\n");
__asm("pop ebp\n");
real_optionloop_after_pressing_yellow();
}
/* R-RANDOM hook */
void (*real_get_random)();
void r_random() {
if (p_record) {
// load lanes from rec.bin
uint8_t *load_button = &recbinArray_loaded[1].pad[0];
uint32_t bt = *(uint32_t *)button_addr;
uint8_t i = 0;
do
{
*(uint16_t *)(bt + 2*i) = *load_button;
++i;
load_button++;
} while (i < 9);
} else if (!p_record && r_ran && ((uint8_t)rec_options <2)) {
uint8_t *g_options_addr;
uint8_t stage_no = 0;
uint16_t *bt_0;
uint16_t *v6;
uint16_t v7;
uint16_t v9;
uint32_t option_offset = 0;
__asm("push 0\n");
__asm("mov ebx, %0\n"::"b"(*button_addr));
__asm("call %0\n"::"a"(ran_func));
__asm("add esp, 4\n");
bt_0 = *(uint16_t **)button_addr;
if (*bt_0 == 0) bt_0++;
v9 = *bt_0; // start No.
uint8_t orig_plop = (uint8_t)rec_options;
stage_no = *(uint8_t*)(**player_options_addr +0x0E);
option_offset = player_option_offset * stage_no;
g_options_addr = (uint8_t*)(**player_options_addr +0x34 +option_offset);
*g_options_addr = 1;
// 0:regular 1:mirror 2:random 3:S-random
if (orig_plop == 0) {
uint32_t bt = *(uint32_t *)button_addr;
uint8_t i = 0;
do
{
*(uint16_t *)(bt + 2*i) = i;
++i;
} while (i < 9);
} else if (orig_plop == 1) {
uint32_t bt = *(uint32_t *)button_addr;
uint8_t i = 0;
do
{
*(uint16_t *)(bt + 2*i) = 8 - i;
++i;
} while (i < 9);
}
// startNo + BaseNo -> R-ran
uint32_t bt = *(uint32_t *)button_addr;
uint8_t i = 0;
do
{
v6 = (uint16_t *)(bt + 2*i);
v7 = v9 + *v6;
if (v7 >= 9) {
v7 -= 9;
}
*v6 = v7;
++i;
} while (i < 9);
}
real_get_random();
}
/* Get address for Special Menu */
uint32_t *g_rend_addr;
uint32_t *font_color;
uint32_t *font_rend_func;
static bool get_rendaddr()
{
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
{
int64_t pattern_offset = _search(data, dllSize,
"\x3b\xC3\x74\x13\xC7\x00\x02\x00\x00\x00\x89\x58\x04\x89\x58\x08", 16, 0);
if (pattern_offset == -1) {
return false;
}
g_rend_addr = (uint32_t *)((int64_t)data + pattern_offset -4);
font_color = (uint32_t *)((int64_t)data + pattern_offset +0x24);
}
{
int64_t pattern_offset = _search(data, dllSize,
"\xC3\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\x8B\x4C\x24\x0C\x8D\x44\x24\x10", 24, 0);
if (pattern_offset == -1) {
return false;
}
font_rend_func = (uint32_t *)((int64_t)data + pattern_offset +16);
}
#if DEBUG == 1
LOG("popnhax: get_rendaddr: g_rend_addr is 0x%X\n", *g_rend_addr);
LOG("popnhax: get_rendaddr: font_color is 0x%X\n", *font_color);
LOG("popnhax: get_rendaddr: font_rend_func is 0x%p\n", font_rend_func);
#endif
return true;
}
/* 2dx speed change */
uint32_t *g_2dx_buffer = 0;
void wavheader_rewrite() {
/* [edi] = xx.2dx
0x00 - 0x17 : 2dx header /x32/x44/x58/x39...
0x18 (4byte) : Chunk ID (RIFFheader) /x52/x49/x46/x46
0x1C (4byte) : Chunk Data Size
0x20 (4byte) : RIFF Type /x57/x41/x56/x45
0x24 (4byte) : Chunk ID /x66/x6D/x74/x20
0x28 (4byte) : Chunk Data Size
0x2C (2byte) : Compression Code , 0x0002=MS ADPCM
0x2E (2byte) : Number of Channels , 0x0002=stereo
0x30 (4byte) : Sample Rate , 0x0000AC44=44100Hz
0x34 (4byte) : byte/sec
*/
uint32_t temp = 0;
temp = *(g_2dx_buffer + 0x0C); //0x0C*4=0x30 SampleRate
temp = (uint32_t)((double)temp*mul_2dx);
*(g_2dx_buffer + 0x0C) = temp;
// next step
temp = *(g_2dx_buffer + 0x0D); //0x0D*4=0x34 byte per sec.
temp = (uint32_t)((double)temp*mul_2dx);
*(g_2dx_buffer + 0x0D) = temp;
}
void (*real_2dx_addr)();
void ex_2dx_speed() {
__asm("mov eax, ebp\n");
__asm("mov %0, edi\n":"=D"(g_2dx_buffer): :);
if(mul_2dx !=1) {
/* Is error avoidance necessary? */
// if(g_2dx_buffer == NULL) mul_2dx = 1;
wavheader_rewrite();
}
real_2dx_addr();
}
/* chart speed change */
uint32_t chart_rows_count = 0;
void chart_rewrite() {
struct CHART {
uint32_t timestamp;
uint16_t operation;
uint16_t data;
uint32_t duration;
} ;
uint16_t i;
struct CHART* real_chart;
double mul_chart = 1.;
real_chart = (CHART*)chartbase_addr;
mul_chart = (double)(100./((double)new_speed));
mul_2dx = (double)(((double)new_speed)/100.);
for (i = 0; i < chart_rows_count; i++) {
real_chart[i].timestamp = (uint32_t)((double)real_chart[i].timestamp*mul_chart);
if (real_chart[i].duration > 0) {
real_chart[i].duration = (uint32_t)((double)real_chart[i].duration*mul_chart);
}
if (real_chart[i].operation == 0x0445) {
uint16_t bpm_orig = 0;
bpm_orig = real_chart[i].data;
if (regul_flag) {
real_chart[i].data = 0x64; // BPM set to 100
} else {
real_chart[i].data = (uint16_t)((double)real_chart[i].data*mul_2dx);
}
LOG("popnhax: BPM change %d -> %d \n", bpm_orig, real_chart[i].data);
}
}
use_sp_flag = true;
LOG("popnhax: chart speed change done\n");
#if DEBUG == 1
LOG("popnhax: chartbase_addr is 0x%X\n", chartbase_addr);
LOG("popnhax: real_chart_addr is 0x%p\n", real_chart);
LOG("popnhax: chart mul_chart is %f\n", mul_chart);
LOG("popnhax: 2dx mul is %f\n", mul_2dx);
#endif
}
void (*real_chart_addr)();
void ex_chart_speed() {
__asm("mov %0, ebx\n":"=b"(chart_rows_count): :); // rows * 0x0C = chart_size
is_resultscreen_flag = false;
stop_input = true; // Because the timing of loading charts and 2dx is different
recsavefile = false;
sp_reset =false;
__asm("call %0\n"::"a"(demoflag_func));
__asm("cmp al, 1\n");
demo_flag = true;
__asm("je demoplay\n");
demo_flag = false;
if (rec_reload) {
**usbpad_addr = 0;
rec_reload = false;
g_return_to_options = false;
disp = true;
hide_menu = true;
stop_recchange = true;
} else {
**usbpad_addr = 1;
hide_menu = false;
disp = false;
if (find_recdata) {
/* We need to be able to choose whether to play or record again
if we come back to the option with a quick retry instead of reloading */
stop_recchange = false;
/* rec.bin header initialize */
uint32_t start_addr = (uint32_t)&recbinArray_loaded[2].timestamp;
uint32_t *play_ptr = (uint32_t*)&recbinArray_loaded[0].judge;
*play_ptr = start_addr;
recbinArray_loaded[0].recid = chartbase_addr;
recbinArray_loaded[0].timing = 0;
} else {
/* Search for record files only when loading a chart for the first time */
record_playdata_start();
}
}
if (new_speed !=100 || regul_flag) {
chart_rewrite();
}
__asm("demoplay:\n");
real_chart_addr();
}
#define COLOR_WHITE 0x00
#define COLOR_GREEN 0x10
#define COLOR_DARK_GREY 0x20
#define COLOR_YELLOW 0x30
#define COLOR_RED 0x40
#define COLOR_BLUE 0x50
#define COLOR_LIGHT_GREY 0x60
#define COLOR_LIGHT_PURPLE 0x70
#define COLOR_BLACK 0xA0
#define COLOR_ORANGE 0xC0
/* ----------- r2nk226 ----------- */
#if DEBUG == 1
const char dmenu_1[] = "is_resultscreen_flag >> %d";
const char dmenu_2[] = "disp >> %d";
const char dmenu_3[] = "use_sp_flag >> %d";
const char dmenu_17[] = "hide_menu >> %d";
const char dmenu_4[] = "stop_input >> %d";
const char dmenu_5[] = "stop_recchange >> %d";
const char dmenu_6[] = "p_record >> %d";
const char dmenu_7[] = "find_recdata >> %d";
const char dmenu_8[] = "speed >> %d";
const char dmenu_9[] = "rec_reload >> %d";
const char dmenu_10[] = "recsavefile >> %d";
const char dmenu_11[] = "rec_SPflags >> %02X";
const char dmenu_12[] = "rec_options >> %04X";
const char dmenu_13[] = "rec_hispeed >> %d";
void r2nk_debug() {
__asm("push ebp\n");
__asm("mov ebp, 0xD0\n");
__asm("movzx ecx, %0\n"::"c"(spec));
__asm("cmp ecx, 0x42\n");
__asm("je shift_x\n");
__asm("sub ebp, 0x40\n");
__asm("shift_x:\n");
__asm("call_dmenu9:\n");
__asm("movzx eax, %0\n"::"a"(rec_reload));
__asm("push eax\n");
__asm("push %0\n"::"D"(dmenu_9));
__asm("push 0x110\n");
__asm("push ebp\n");
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
__asm("call_dmenu1:\n");
__asm("movzx eax, %0\n"::"a"(is_resultscreen_flag));
__asm("push eax\n");
__asm("push %0\n"::"D"(dmenu_1));
__asm("push 0x120\n");
__asm("push ebp\n");
//__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
__asm("call_dmenu2:\n");
__asm("movzx eax, %0\n"::"a"(disp));
__asm("push eax\n");
__asm("push %0\n"::"D"(dmenu_2));
__asm("push 0x130\n");
__asm("push ebp\n");
//__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
__asm("call_dmenu3:\n");
__asm("movzx eax, %0\n"::"a"(use_sp_flag));
__asm("push eax\n");
__asm("push %0\n"::"D"(dmenu_3));
__asm("push 0x140\n");
__asm("push ebp\n");
//__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
__asm("call_dmenu4:\n");
__asm("movzx eax, %0\n"::"a"(stop_input));
__asm("push eax\n");
__asm("push %0\n"::"D"(dmenu_4));
__asm("push 0x150\n");
__asm("push ebp\n");
//__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
__asm("call_dmenu5:\n");
__asm("movzx eax, %0\n"::"a"(stop_recchange));
__asm("push eax\n");
__asm("push %0\n"::"D"(dmenu_5));
__asm("push 0x160\n");
__asm("push ebp\n");
//__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
__asm("call_dmenu6:\n");
__asm("movzx eax, %0\n"::"a"(p_record));
__asm("push eax\n");
__asm("push %0\n"::"D"(dmenu_6));
__asm("push 0x170\n");
__asm("push ebp\n");
//__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
__asm("call_dmenu7:\n");
__asm("movzx eax, %0\n"::"a"(find_recdata));
__asm("push eax\n");
__asm("push %0\n"::"D"(dmenu_7));
__asm("push 0x180\n");
__asm("push ebp\n");
//__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
__asm("call_dmenu8:\n");
__asm("movzx eax, %0\n"::"a"(speed));
__asm("push eax\n");
__asm("push %0\n"::"D"(dmenu_8));
__asm("push 0x190\n");
__asm("push ebp\n");
//__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
__asm("call_dmenu10:\n");
__asm("movzx eax, %0\n"::"a"(recsavefile));
__asm("push eax\n");
__asm("push %0\n"::"D"(dmenu_10));
__asm("push 0x1A0\n");
__asm("push ebp\n");
//__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
__asm("call_dmenu11:\n");
__asm("movzx eax, %0\n"::"a"(rec_SPflags));
__asm("push eax\n");
__asm("push %0\n"::"D"(dmenu_11));
__asm("push 0x1B0\n");
__asm("push ebp\n");
//__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
__asm("call_dmenu12:\n");
__asm("push %0\n"::"a"(rec_options));
__asm("push %0\n"::"D"(dmenu_12));
__asm("push 0x1C0\n");
__asm("push ebp\n");
//__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
__asm("call_dmenu13:\n");
__asm("movzx eax, %0\n"::"a"(rec_hispeed));
__asm("push eax\n");
__asm("push %0\n"::"D"(dmenu_13));
__asm("push 0x1D0\n");
__asm("push ebp\n");
//__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
__asm("call_dmenu17:\n");
__asm("movzx eax, %0\n"::"a"(hide_menu));
__asm("push eax\n");
__asm("push %0\n"::"D"(dmenu_17));
__asm("push 0x100\n");
__asm("push ebp\n");
//__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
__asm("pop ebp\n");
}
#endif
void enhanced_polling_stats_disp_sub();
const char menu_1[] = "-- PracticeMode(pad7) --";
const char menu_2[] = "NO CONTEST";
const char menu_3[] = "CONSTANT (pad4) >> %s";
const char menu_4[] = "R-RANDOM (pad6) >> %s";
const char *menu_6_str[2] = {"quick retire (pad9)", "quit pfree mode (pad9)"};
const char menu_7[] = "quick retry (pad8)";
const char *menu_9_str[2] = {"RECORD (pad00) >> %s", "SAVE FILE (pad00)%s"};
const char *menu_10_str[3] = {"No data", "Record Available", "OK"};
const char *menu_11_str[2] = { "Ready to Save", "Replay Results",};
const char menu_12[] = "SPEED (pad5) >> %d%%";
const char *onoff_str[2] = {"OFF", "ON"};
const char *recplay_str[3] = {"rec", "play", " "};
void (*real_aging_loop)();
void new_menu() {
__asm("mov eax, [%0]\n"::"a"(*g_rend_addr));
__asm("cmp eax, 0\n");
__asm("je call_menu\n");
__asm("mov dword ptr [eax], 2\n");
if (is_resultscreen_flag) {
__asm("mov dword ptr [eax], 1\n");
}
__asm("mov dword ptr [eax+4], 1\n");
__asm("mov dword ptr [eax+8], 0\n");
__asm("mov dword ptr [eax+0x34], 1\n");
__asm("movzx ecx, %0\n"::"c"(spec));
__asm("sub ecx, 0x42\n");
__asm("sete cl\n");
__asm("add byte ptr [eax+4], cl\n");
__asm("lea ecx, dword ptr [%0+2]\n"::"a"(input_func));
__asm("mov ecx, dword ptr [ecx]\n");
__asm("mov ecx, dword ptr [ecx]\n");
__asm("cmp ecx, 0\n");
__asm("je call_menu\n");
__asm("cmp dword ptr [ecx+0x80], 5\n");
__asm("je call_menu\n");
flag_reset(); // title screen
/* Practice mode MENU */
__asm("call_menu:\n");
__asm("push %0\n"::"a"(menu_1));
__asm("push 0x150\n");
__asm("push 0x2E0\n");
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_BLUE));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x0C\n");
/* NO CONTEST part1 */
if (is_resultscreen_flag) {
// Erase scores and medals after results screen is displayed
**usbpad_addr = 1;
if (!sp_reset) {
if (!p_record) {
save_recSPflags();
}
practice_scoremedal_reset();
}
// initialize rec.bin header when using instant launch song (hold 8 quick retry)
if (p_record && g_return_to_options) {
uint32_t start_addr = (uint32_t)&recbinArray_loaded[2].timestamp;
uint32_t *play_ptr = (uint32_t*)&recbinArray_loaded[0].judge;
*play_ptr = start_addr;
recbinArray_loaded[0].recid = chartbase_addr;
recbinArray_loaded[0].timing = 0;
}
}
/* NO CONTEST part2 */
if (use_sp_flag) {
__asm("push %0\n"::"a"(menu_2));
__asm("push 0x160\n");
__asm("push 0x2E0\n");
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_YELLOW));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x0C\n");
}
/* quick menu on/off */
if (disp) {
/* quick retry */
__asm("push %0\n"::"a"(menu_7));
__asm("push 0x1C0\n");
__asm("push 0x2E0\n");
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_GREEN));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x0C\n");
/* quick retire & exit */
__asm("push %0\n"::"a"(menu_6_str[is_resultscreen_flag]));
__asm("push 0x1D0\n");
__asm("push 0x2E0\n");
//__asm("mov esi, %0\n"::"a"(*font_color+COLOR_GREEN));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x0C\n");
}
/* hide_menu */
if (!is_resultscreen_flag && disp) {
hide_menu = true; // Menu off while playing
} else {
__asm("mov ecx, 7\n");
__asm("call %0\n"::"a"(input_func));
__asm("test al, al\n");
__asm("je SW_nodisp\n");
__asm("push edx\n");
__asm("mov eax, 0x09\n");
if (!hide_menu) {
__asm("add eax, 0x16\n");
}
__asm("push 0\n");
__asm("call %0\n"::"D"(playsramsound_func));
__asm("add esp, 4\n");
__asm("pop edx\n");
hide_menu ^= 1;
__asm("SW_nodisp:\n");
}
if (!hide_menu) {
/* Record savefile */
if (stop_input) {
if (is_resultscreen_flag) {
if (recsavefile) {
__asm("push %0\n"::"a"(menu_10_str[2])); // OK
} else {
// (rec) 11_0 FReady2Save (play) 11_1 FRecResult
__asm("push %0\n"::"a"(menu_11_str[p_record]));
}
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
} else if (!is_resultscreen_flag) {
// (find_recdata) 10_1 FRecAva (!find) 10_0 FNo data
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_DARK_GREY)); // No Data
if (find_recdata) {
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED)); // Record Available
}
__asm("push %0\n"::"a"(menu_10_str[find_recdata]));
}
__asm("push 0x180\n");
__asm("push 0x2E0\n");
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x0C\n");
}
if (!stop_recchange) {
if (find_recdata) {
__asm("mov ecx, 0x0B\n");
__asm("call %0\n"::"a"(input_func));
__asm("test al, al\n");
__asm("je SW_00\n");
p_record ^= 1;
__asm("SW_00:\n");
}
} else if (stop_recchange && is_resultscreen_flag && !recsavefile) {
__asm("mov ecx, 0x0B\n");
__asm("call %0\n"::"a"(input_func));
__asm("test al, al\n");
__asm("je notsave\n");
if(!recdata_save()) {
LOG("popnhax: file save failure\n");
}
__asm("notsave:\n");
}
/* Accept input only when music is selected */
if (!stop_input) {
__asm("mov ecx, 4\n");
__asm("call %0\n"::"a"(input_func));
__asm("test al, al\n");
__asm("je SW_4\n");
regul_flag ^= 1;
__asm("SW_4:\n");
__asm("mov ecx, 6\n");
__asm("call %0\n"::"a"(input_func));
__asm("test al, al\n");
__asm("je SW_6\n");
r_ran ^= 1;
__asm("SW_6:\n");
__asm("mov ecx, 5\n");
__asm("call %0\n"::"a"(input_func));
__asm("test al, al\n");
__asm("je SW_5\n");
speed++;
if (speed > 10) {
speed = 0;
new_speed = 150;
}
new_speed = 150 - (speed * 10);
__asm("SW_5:\n");
}
/* Record mode */
if (is_resultscreen_flag) {
if (!recsavefile) {
int8_t m9_no = 2-p_record;
__asm("push %0\n"::"a"(recplay_str[m9_no]));
__asm("push %0\n"::"a"(menu_9_str[!p_record]));
__asm("push 0x170\n");
__asm("push 0x2E0\n");
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
}
} else if (!is_resultscreen_flag) {
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_DARK_GREY));
if (find_recdata) {
__asm("push %0\n"::"a"(recplay_str[p_record])); // rec or play
__asm("push %0\n"::"a"(menu_9_str[0]));
__asm("push 0x170\n");
__asm("push 0x2E0\n");
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
if (p_record) {
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_GREEN));
}
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
}
}
/* CONSTANT SPEED no-soflan */
__asm("push %0\n"::"D"(onoff_str[regul_flag]));
__asm("push %0\n"::"a"(menu_3));
__asm("push 0x190\n");
__asm("push 0x2E0\n");
if (stop_input) {
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_DARK_GREY));
if (is_resultscreen_flag) {
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
}
} else {
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_WHITE));
if (regul_flag) {
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
}
}
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
/* R-RANDOM */
__asm("push %0\n"::"D"(onoff_str[r_ran]));
__asm("push %0\n"::"a"(menu_4));
__asm("push 0x1A0\n");
__asm("push 0x2E0\n");
if (stop_input) {
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_DARK_GREY));
if (is_resultscreen_flag) {
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
}
} else {
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_WHITE));
if (r_ran) {
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
}
}
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
/* SPEED */
__asm("push %0\n"::"D"(new_speed));
__asm("push %0\n"::"a"(menu_12));
__asm("push 0x1B0\n");
__asm("push 0x2E0\n");
if (stop_input) {
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_DARK_GREY));
if (is_resultscreen_flag) {
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
}
} else {
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_WHITE));
if (new_speed != 100) {
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_RED));
}
}
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
}
__asm("mov eax, [%0]\n"::"a"(*g_rend_addr));
__asm("mov dword ptr [eax], 2\n");
__asm("mov dword ptr [eax+4], 1\n");
__asm("mov dword ptr [eax+8], 0\n");
__asm("mov dword ptr [eax+0x34], 1\n");
#if DEBUG == 1
if (g_error_str != NULL) {
printf("%s\n", g_error_str);
g_error_str = NULL;
}
r2nk_debug();
#endif
if (config.enhanced_polling && config.enhanced_polling_stats)
enhanced_polling_stats_disp_sub();
real_aging_loop();
}
static bool patch_practice_mode()
{
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
{
/* AGING MODE to Practice Mode */
int64_t pattern_offset = _search(data, dllSize-0x100000,
"\x83\xEC\x40\x53\x56\x57", 6, 0x100000);
if (pattern_offset == -1) {
LOG("popnhax: cannot retrieve aging loop\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 6;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)new_menu,
(void **)&real_aging_loop);
#if DEBUG == 1
LOG("popnhax: practice_mode: aging_hook addr is 0x%llX\n", patch_addr);
#endif
}
{
if (!get_rendaddr())
{
LOG("popnhax: Cannot find address for drawing\n");
return false;
}
}
{
/* INPUT numkey */
int64_t pattern_offset = _search(data, dllSize,
"\x85\xC9\x74\x08\x8B\x01\x8B\x40\x24\x52\xFF\xD0", 12, 0);
if (pattern_offset == -1) {
LOG("popnhax: Cannot find input_func address\n");
return false;
}
input_func = (uint32_t *)((int64_t)data + pattern_offset + 0x1A);
#if DEBUG == 1
LOG("popnhax: InputNumPad_addr is 0x%p\n", input_func);
#endif
}
{
/* player_options_addr */
int64_t pattern_offset = _search(data, dllSize,
"\x14\xFF\xE2\xC3\xCC\xCC", 6, 0);
if (pattern_offset == -1) {
LOG("popnhax: Cannot find player_options_addr\n");
return false;
}
player_options_addr = (uint32_t **)((int64_t)data + pattern_offset + 0x31);
}
/* speed change */
{
// first step : 2dx hook
int64_t pattern_offset = _search(data, dllSize,
"\x83\xC4\x0C\x8B\xC3\x8D\x7C\x24", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: Cannot find 2dxLoad address\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset +0x10;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)ex_2dx_speed,
(void **)&real_2dx_addr);
#if DEBUG == 1
LOG("popnhax: 2dxHook_addr is 0x%llX\n", patch_addr);
#endif
}
{
// second step : chart hook
int64_t pattern_offset = _search(data, dllSize,
"\x8B\x74\x24\x18\x8D\x0C\x5B\x8B\x54\x8E\xF4", 11, 0);
if (pattern_offset == -1) {
LOG("popnhax: Cannot find chartLoad address\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)ex_chart_speed,
(void **)&real_chart_addr);
#if DEBUG == 1
LOG("popnhax: chartHook_addr is 0x%llX\n", patch_addr);
#endif
}
LOG("popnhax: speed hook enabled\n");
/* r_random hook */
{
// random_function_addr
int64_t pattern_offset = _search(data, dllSize,
"\x51\x55\x56\xC7\x44\x24\x08\x00\x00\x00", 10, 0);
if (pattern_offset == -1) {
LOG("popnhax: Cannot find random_function address\n");
return false;
}
ran_func = (uint32_t *)((int64_t)data + pattern_offset);
}
{
// button_addr
int64_t pattern_offset = _search(data, dllSize,
"\x03\xC5\x83\xF8\x09\x7C\xDE", 7, 0);
if (pattern_offset == -1) {
LOG("popnhax: Cannot find button address\n");
return false;
}
button_addr = (uint32_t *)((int64_t)data + pattern_offset -0x14);
}
{
// r-ran hook addr
int64_t pattern_offset = _search(data, dllSize,
"\x83\xC4\x04\xB9\x02\x00\x00\x00", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: Cannot find r-ran hook addr\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset -0x13;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)r_random,
(void **)&real_get_random);
}
#if DEBUG == 1
LOG("popnhax: get_addr_random: ran_func_addr is 0x%p\n", ran_func);
LOG("popnhax: get_addr_random: button_addr is 0x%X\n", *button_addr);
#endif
LOG("popnhax: R-Random hook enabled\n");
return true;
}
/* ----------------------- */
/* add a new feature */
/* ----------------------- */
static bool patch_record_mode(bool quickretire)
{
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
/* If quickretire is not installed, it must be installed for reloading */
if (!quickretire) {
{
/* hook quick retire transition to go back to option select instead */
int64_t pattern_offset = _search(data, dllSize,
"\x8B\xE8\x8B\x47\x30\x83\xF8\x17", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: cannot retrieve screen transition function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)quickexit_screen_transition,
(void **)&real_screen_transition);
}
/* retrieve songstart function pointer for quick retry */
{
int64_t pattern_offset = _search(data, dllSize,
"\xE9\x0C\x01\x00\x00\x8B\x85", 7, 0);
int delta = -4;
if (pattern_offset == -1) {
delta = 18;
pattern_offset = _search(data, dllSize,
"\x6A\x00\xB8\x17\x00\x00\x00\xE8", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: cannot retrieve song start function\n");
return false;
}
}
uint64_t patch_addr = (int64_t)data + pattern_offset + delta;
g_startsong_addr = *(uint32_t*)(patch_addr);
}
/* instant launch song with numpad 8 on option select (hold 8 during song for quick retry) */
{
int64_t pattern_offset = _search(data, dllSize,
"\x8B\xF0\x83\x7E\x0C\x00\x0F\x84", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: cannot retrieve option screen loop\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset - 0x0F;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)quickexit_option_screen_simple,
(void **)&real_option_screen_simple);
}
LOG("popnhax: record: reloading enabled\n");
}
/* record_mode hook */
{
//??_7CMusicSelectScene@@6B@
int64_t pattern_offset = _search(data, dllSize,
"\x8B\x44\x24\x04\x56\x57\x50\x8B\xF9", 9, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: MusicSelectScene_addr was not found\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_musicselect,
(void **)&real_musicselect);
}
{
// g_elapsed_time
int64_t pattern_offset = _search(data, dllSize,
"\x02\x8B\xF0\x7C", 4, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: elapsed_time_addr was not found\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset +1;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)get_elapsed_time,
(void **)&get_elapsed_time_hook);
}
{
// ifs_name
int64_t pattern_offset = _search(data, dllSize,
"\x83\xC4\x04\x50\x8B\xC7\x85\xDB", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: ifs_name_ptr was not found\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)get_ifs_filename,
(void **)&get_ifs_name);
}
{
// for reload
int64_t pattern_offset = _search(data, dllSize,
"\xE9\x0C\x01\x00\x00\x8B\x85", 7, 0);
int delta = -8;
if (pattern_offset == -1) {
delta = 12;
pattern_offset = _search(data, dllSize,
"\x6A\x00\xB8\x17\x00\x00\x00\xE8", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: cannot retrieve song start function (2)\n");
return false;
}
}
uint64_t patch_addr = (int64_t)data + pattern_offset + delta;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_optionloop_after_pressing_red,
(void **)&real_optionloop_after_pressing_red);
}
{
// next step (after pressing yellow)
int64_t pattern_offset = _search(data, dllSize,
"\x8B\x55\x00\x8B\x82\x9C\x00\x00\x00\x6A\x01\x8B\xCD\xFF\xD0\x80\xBD", 17, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: cannot retrieve option screen yellow leave addr\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset +0x2C;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_optionloop_after_pressing_yellow,
(void **)&real_optionloop_after_pressing_yellow);
}
{
// play1_addr(judge start)
int64_t pattern_offset = _search(data, dllSize,
"\xC1\xE8\x07\x24\x01\x8A\xD8", 7, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: play1_addr was not found\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)play_firststep,
(void **)&hook_playfirst);
}
{
// play3_addr (first_auto_flag_check)
int64_t pattern_offset = _search(data, dllSize,
"\x84\xC0\x0F\x84\x08\x01\x00\x00", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: play3_addr was not found\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
uint32_t g_calladdr_offset = *(uint32_t*)((int64_t)data + pattern_offset -4);
demoflag_func = (int64_t)data + pattern_offset +g_calladdr_offset;
#if DEBUG == 1
LOG("debug: g_calladdr_offset is 0x%d\n", g_calladdr_offset);
LOG("debug: demoflag_func is 0x%X\n", demoflag_func);
#endif
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)play_thirdstep,
(void **)&first_auto_flag_check);
// play2_addr (last_auto_flag_check) , p_note
int64_t pattern_offset_p2 = _search(data, dllSize,
"\x84\xC0\x74\x53", 4, pattern_offset);
if (pattern_offset_p2 == -1) {
LOG("popnhax: record: play2_addr was not found\n");
return false;
}
p_note = (uint32_t*)((int64_t)data + pattern_offset_p2 +6);
uint64_t patch_addr_p2 = (int64_t)data + pattern_offset_p2;
_MH_CreateHook((LPVOID)(patch_addr_p2), (LPVOID)play_secondstep,
(void **)&last_auto_flag_check);
}
{
// play4_addr(long_end_flow)
int64_t pattern_offset = _search(data, dllSize,
"\x83\xC4\x04\xEB\x2E\xBA\x80\x00\x00\x00", 10, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: play4_addr was not found\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset -11;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)play_fourthstep,
(void **)&long_end_flow);
}
{
// rec1_addr, judge_bar_func
int64_t pattern_offset = _search(data, dllSize,
"\xE3\x00\x00\x83\xC4\x0C\x80\x7C\x24", 9, 0);
if (pattern_offset == -1) {
//next search
pattern_offset = _search(data, dllSize,
"\xE4\x00\x00\x83\xC4\x0C\x80\x7C\x24", 9, 0);
if (pattern_offset == -1) {
//next search for 28
pattern_offset = _search(data, dllSize,
"\xE6\x00\x00\x83\xC4\x0C\x80\x7C\x24", 9, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: rec1_addr was not found\n");
return false;
}
}
}
uint32_t *tmp_addr = (uint32_t*)((int64_t)data + pattern_offset -1);
judge_bar_func = (uint32_t)((int64_t)data +pattern_offset + *tmp_addr +3);
uint64_t patch_addr = (int64_t)data + pattern_offset -2;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)record_playdata,
(void **)&get_judge);
}
{
// rec2_addr
int64_t pattern_offset = _search(data, dllSize,
"\x24\x0F\x66\x0F\xB6\xC8\x66\xC1", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: rec2_addr was not found\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset +0x10;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)record_playdata_poor,
(void **)&get_poor);
}
{
// rec1 enhanced_poll fix
int64_t pattern_offset = _search(data, dllSize, "\x8B\x38\xBB\x07\x00\x00\x00", 7, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: cannot find rec1 fix address\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)record_timing_override,
(void **)&pre_judge_time);
}
/* other functions */
{
// PlaySramSound func
int64_t pattern_offset = _search(data, dllSize,
"\x51\x56\x8B\xF0\x85\xF6\x74\x6C\x6B\xC0\x2C", 11, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: PlaySramSound_addr was not found\n");
return false;
}
playsramsound_func = (uint32_t)((int64_t)data + pattern_offset);
}
{
// j_win_addr
int64_t pattern_offset = _search(data, dllSize,
"\x84\xC0\x74\x18\x8B\x04", 6, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: j_win_addr was not found\n");
return false;
}
j_win_addr = (uint32_t)((int64_t)data + pattern_offset +7);
}
{
// for rec_date
int64_t pattern_offset = _search(data, dllSize,
"\x83\xEC\x2C\x6A\x00", 5, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: date_func was not found\n");
return false;
}
date_func = (uint32_t)((int64_t)data + pattern_offset);
}
{
// for no-pfree
int64_t pattern_offset = _search(data, dllSize,
"\x83\xF8\x04\x0F\xB6\xC1\x75\x13\x69\xC0", 10, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: cannot find player-option offset\n");
return false;
}
player_option_offset = *(uint32_t *)((int64_t)data + pattern_offset +0x0A);
}
{
// usbPadReadLast
int64_t pattern_offset = _search(data, dllSize, "\x83\xC4\x04\x5D\xC3\xCC\xCC", 7, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: cannot find usbPadRead call (1)\n");
return false;
}
pattern_offset = _search(data, dllSize-pattern_offset-1, "\x83\xC4\x04\x5D\xC3\xCC\xCC", 7, pattern_offset+1);
if (pattern_offset == -1) {
LOG("popnhax: record: cannot find usbPadRead call (2)\n");
return false;
}
usbpad_addr = (uint32_t**)((int64_t)data + pattern_offset -0x21);
}
{
// recmode pop-kun change
// Check if custom popkuns are present
if (access("data_mods\\_popnhax_assets\\tex\\gmcmh.ifs", F_OK) == 0)
{
LOG("popnhax: record: custom popkun assets found. Using %s\n", popkun_change);
int64_t pattern_offset = _search(data, dllSize,
"\x5E\x83\xC4\x10\xC3\x51\xE8", 7, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: ifs load address not found\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset +13;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)loadtexhook,
(void **)&gm_ifs_load);
#if DEBUG == 1
LOG("debug: loadtexhook_addr is 0x%llX\n", patch_addr);
#endif
}
else
{
LOG("popnhax: record: custom popkun assets not found. Using regular ones\n");
}
}
{
// Reset 2dx magnification setting
int64_t pattern_offset = _search(data, dllSize,
"\x83\xC8\xFF\x33\xC9\x33\xD2\x66\x89", 9, 0);
if (pattern_offset == -1) {
LOG("popnhax: record: cannot find load status addr\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)reset_2dx_magnification_setting,
(void **)&flag_status);
#if DEBUG == 1
LOG("debug: 2dx_reset_addr is 0x%llX\n", patch_addr);
#endif
}
#if DEBUG == 1
LOG("debug: p_note is 0x%p, value is 0x%X\n", p_note, *p_note);
LOG("debug: usbpad_addr is 0x%p, value is 0x%X\n", usbpad_addr, **usbpad_addr);
LOG("debug: player_option_offset value is 0x%X\n", player_option_offset);
LOG("debug: j_win_addr is 0x%X\n", j_win_addr);
LOG("debug: judge_bar_func is 0x%X\n", judge_bar_func);
LOG("debug: playsramsound_func is 0x%X\n", playsramsound_func);
LOG("debug: date_func_addr is 0x%X\n", date_func);
LOG("debug: recreload is 0x%p\n", (void*)&rec_reload);
#endif
LOG("popnhax: record: rec_mode enabled\n");
return true;
}
static bool patch_silent_mode() {
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
int64_t pattern_offset = _search(data, dllSize, "\x3B\xC1\x1B\xC0\x23\xC1\xA3", 7, 0);
if (pattern_offset == -1) {
LOG("popnhax: silent patch error\n");
return false;
}
uint32_t **default_volume_pre = (uint32_t **)((int64_t)data + pattern_offset +7);
uint32_t *default_volume = *default_volume_pre;
DWORD old_prot;
VirtualProtect((LPVOID)default_volume, 4, PAGE_EXECUTE_READWRITE, &old_prot);
*default_volume = 0;
VirtualProtect((LPVOID)default_volume, 4, old_prot, &old_prot);
uint64_t patch_addr = (int64_t)data + pattern_offset;
patch_memory(patch_addr, (char *)"\x90\x90\x90\x90\x90\x90", 6);
for (int i=0; i<3; i++) {
if (!find_and_patch_hex(g_game_dll_fn, "\x33\xC0\x4B\x3B\xC1\x1B", 6, 3, "\xEB\x0E", 2)) {
LOG("popnhax: silent patch error (%d)\n", i);
return false;
}
}
if (!find_and_patch_hex(g_game_dll_fn, "\x83\xEC\x08\x89\x4C\x24\x08", 7, 7, "\x33\xC0\x90\x90\x90", 5)) {
LOG("popnhax: silent patch error (3)\n");
return false;
}
if (!find_and_patch_hex(g_game_dll_fn, "\x8B\xD6\x6B\xD2\x2C\x89\x82", 7, -0x22, "\x33\xD2\x90\x90\x90", 5)) {
LOG("popnhax: silent patch error (5)\n");
return false;
}
if (!find_and_patch_hex(g_game_dll_fn, "\x8B\xD6\x6B\xD2\x2C\x89\x82", 7, 5, "\x33\xC0\x90\x90\x90\x90", 6)) {
LOG("popnhax: silent patch error (4)\n");
return false;
}
return true;
}
void (*real_hariai_error)();
void hariai_error_skip() {
__asm("test esi, esi\n");
__asm("jne ha_return\n");
__asm("lea ecx, dword ptr [esp - 0x308]\n");
__asm("ha_return:\n");
real_hariai_error();
}
void (*real_errormessage)();
void errormessage_hook() {
__asm("mov byte ptr [ecx+0x11c], 0\n");
__asm("add ecx, 0x128\n");
__asm("mov %0, ecx\n":"=c"(g_error_str): :);
__asm("sub ecx, 0x128\n");
real_errormessage();
}
static bool patch_hakc_errorskip() {
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
{
int64_t pattern_offset = _search(data, dllSize, "\x8B\xF3\x83\xC4\x0C\x8B\xDF", 7, 0);
if (pattern_offset == -1) {
LOG("popnhax: cannnot find hariai_error_hook_addr (1)\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x12;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hariai_error_skip,
(void **)&real_hariai_error);
}
{
int64_t pattern_offset = _search(data, dllSize, "\x8B\x44\x24\x14\x8B\x7C\x24\x10", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: cannnot find hariai_error_hook_addr (2)\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x0F;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)errormessage_hook,
(void **)&real_errormessage);
}
return true;
}
void (*real_render_loop)();
void enhanced_polling_stats_disp()
{
__asm("mov eax, [%0]\n"::"a"(*g_rend_addr));
__asm("cmp eax, 0\n");
__asm("je call_stat_menu\n");
__asm("mov dword ptr [eax], 2\n");
__asm("mov dword ptr [eax+4], 1\n");
__asm("mov dword ptr [eax+8], 0\n");
__asm("mov dword ptr [eax+0x34], 1\n");
__asm("call_stat_menu:\n");
enhanced_polling_stats_disp_sub();
real_render_loop();
}
/* enhanced_polling_stats */
const char stats_disp_header[] = " - 1000Hz Polling - ";
const char stats_disp_lastpoll[] = "last input: 0x%06x";
const char stats_disp_avgpoll[] = "100 polls in %dms";
const char stats_disp_avgpoll_ho[] = "hardware offload";
const char stats_disp_offset_header[] = "- Latest correction -";
const char stats_disp_offset_top[] = "%s";
char stats_disp_offset_top_str[15] = "";
const char stats_disp_offset_bottom[] = "%s";
char stats_disp_offset_bottom_str[19] = "";
void enhanced_polling_stats_disp_sub()
{
__asm("push %0\n"::"a"(stats_disp_header));
__asm("push 0x2A\n"); //Y coord
__asm("push 0x160\n"); //X coord
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_LIGHT_GREY));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x0C\n");
uint8_t color = COLOR_GREEN;
if (g_poll_rate_avg > 100)
color = COLOR_RED;
__asm("push %0\n"::"D"(g_poll_rate_avg));
if ( g_hardware_offload )
__asm("push %0\n"::"a"(stats_disp_avgpoll_ho));
else
__asm("push %0\n"::"a"(stats_disp_avgpoll));
__asm("push 0x5C\n");
__asm("push 0x160\n");
__asm("mov esi, %0\n"::"a"(*font_color+color));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
__asm("push %0\n"::"D"(g_last_button_state));
__asm("push %0\n"::"a"(stats_disp_lastpoll));
__asm("push 0x48\n");
__asm("push 0x160\n");
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_LIGHT_PURPLE));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
__asm("push %0\n"::"a"(stats_disp_offset_header));
__asm("push 0x2A\n");
__asm("push 0x200\n");
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_LIGHT_GREY));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x0C\n");
sprintf(stats_disp_offset_top_str, "%02u %02u %02u %02u", g_offset_fix[1], g_offset_fix[3], g_offset_fix[5], g_offset_fix[7]);
__asm("push %0\n"::"D"(stats_disp_offset_top_str));
__asm("push %0\n"::"a"(stats_disp_offset_top));
__asm("push 0x48\n");
__asm("push 0x200\n");
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_ORANGE));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
sprintf(stats_disp_offset_bottom_str, "%02u %02u %02u %02u %02u", g_offset_fix[0], g_offset_fix[2], g_offset_fix[4], g_offset_fix[6], g_offset_fix[8]);
__asm("push %0\n"::"D"(stats_disp_offset_bottom_str));
__asm("push %0\n"::"a"(stats_disp_offset_top));
__asm("push 0x5C\n");
__asm("push 0x1FD\n");
__asm("mov esi, %0\n"::"a"(*font_color+COLOR_ORANGE));
__asm("call %0\n"::"a"(font_rend_func));
__asm("add esp, 0x10\n");
}
static bool patch_enhanced_polling_stats()
{
if (config.practice_mode)
{
LOG("popnhax: enhanced polling stats displayed\n");
return false;
}
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
{
int64_t pattern_offset = _search(data, dllSize-0x100000, "\x83\xEC\x40\x53\x56\x57", 6, 0x100000);
if (pattern_offset == -1) {
LOG("popnhax: enhanced_polling_stats: cannot retrieve aging loop\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 6;
if (!get_rendaddr())
{
LOG("popnhax: enhanced_polling_stats: cannot find address for drawing\n");
return false;
}
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)enhanced_polling_stats_disp,
(void **)&real_render_loop);
}
LOG("popnhax: enhanced polling stats displayed\n");
return true;
}
/* HARD GAUGE SURVIVAL*/
uint8_t g_hard_gauge_selected = false;
void (*real_survival_flag_hard_gauge_new)();
void hook_survival_flag_hard_gauge_new()
{
__asm("add eax, 0x36\n");
__asm("cmp byte ptr [eax], 2\n");
g_hard_gauge_selected = false;
__asm("jne no_hard_gauge_new\n");
g_hard_gauge_selected = true;
__asm("no_hard_gauge_new:\n");
__asm("sub eax, 0x36\n");
real_survival_flag_hard_gauge_new();
}
void (*real_survival_flag_hard_gauge)();
void hook_survival_flag_hard_gauge()
{
__asm("cmp bl, 0\n");
__asm("jne no_hard_gauge\n");
g_hard_gauge_selected = false;
__asm("cmp cl, 2\n");
__asm("jne no_hard_gauge\n");
g_hard_gauge_selected = true;
__asm("no_hard_gauge:\n");
real_survival_flag_hard_gauge();
}
void (*real_survival_flag_hard_gauge_old)();
void hook_survival_flag_hard_gauge_old()
{
__asm("cmp bl, 0\n");
__asm("jne no_hard_gauge_old\n");
g_hard_gauge_selected = false;
__asm("cmp dl, 2\n"); //dl is used instead of cl in older games
__asm("jne no_hard_gauge_old\n");
g_hard_gauge_selected = true;
__asm("no_hard_gauge_old:\n");
real_survival_flag_hard_gauge_old();
}
void (*real_check_survival_gauge)();
void hook_check_survival_gauge()
{
if ( g_hard_gauge_selected )
{
__asm("mov al, 1\n");
__asm("ret\n");
}
real_check_survival_gauge();
}
void (*real_survival_gauge_medal_clear)();
void (*real_survival_gauge_medal)();
void hook_survival_gauge_medal()
{
if ( g_hard_gauge_selected )
{
__asm("cmp eax, 0\n"); //empty gauge should still fail
__asm("jz skip_force_clear\n");
/* fix gauge ( [0;1023] -> [725;1023] ) */
__asm("push eax");
__asm("push ebx");
__asm("push edx");
__asm("xor edx,edx");
__asm("mov eax, dword ptr [edi+4]");
/* bigger interval for first bar */
__asm("cmp eax, 42");
__asm("jge skip_lower");
__asm("mov eax, 42");
__asm("skip_lower:");
/* tweak off by one/two values */
__asm("cmp eax, 297");
__asm("je decrease_once");
__asm("cmp eax, 298");
__asm("je decrease_twice");
__asm("cmp eax, 426");
__asm("je decrease_once");
__asm("cmp eax, 681");
__asm("je decrease_once");
__asm("cmp eax, 936");
__asm("je decrease_once");
__asm("cmp eax, 937");
__asm("je decrease_twice");
__asm("jmp no_decrease");
__asm("decrease_twice:");
__asm("dec eax");
__asm("decrease_once:");
__asm("dec eax");
__asm("no_decrease:");
/* perform ((gauge+99)/3) + 678 */
__asm("add eax, 99");
__asm("mov bx, 3");
__asm("idiv bx");
__asm("add eax, 678");
/* higher cap value */
__asm("cmp eax, 1023");
__asm("jle skip_trim");
__asm("mov eax, 1023");
__asm("skip_trim:");
__asm("mov dword ptr [edi+4], eax");
__asm("pop edx");
__asm("pop ebx");
__asm("pop eax");
__asm("jmp %0\n"::"m"(real_survival_gauge_medal_clear));
}
__asm("skip_force_clear:\n");
real_survival_gauge_medal();
}
void (*real_get_retire_timer)();
void hook_get_retire_timer()
{
if ( g_hard_gauge_selected )
{
__asm("mov eax, 0xFFFF\n");
__asm("ret\n");
}
real_get_retire_timer();
}
bool patch_hard_gauge_survival(uint8_t severity)
{
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
/* refill gauge at each stage */
{
if (!find_and_patch_hex(g_game_dll_fn, "\x84\xC0\x75\x0B\xB8\x00\x04\x00\x00", 9, 0, "\x90\x90\x90\x90", 4))
{
LOG("popnhax: survival gauge: cannot patch gauge refill\n");
}
}
/* change is_survival_gauge function behavior */
{
int64_t pattern_offset = _search(data, dllSize, "\x33\xC9\x83\xF8\x04\x0F\x94\xC1\x8A\xC1", 10, 0);
if (pattern_offset == -1) {
LOG("popnhax: survival gauge: cannot find survival gauge check function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x02;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_check_survival_gauge,
(void **)&real_check_survival_gauge);
}
/* change get_retire_timer function behavior (fix bug with song not exiting on empty gauge when paseli is on) */
{
int64_t pattern_offset = _search(data, dllSize, "\x3D\xB0\x04\x00\x00\x7C", 6, 0);
if (pattern_offset == -1) {
LOG("popnhax: survival gauge: cannot find get retire timer function\n");
return false;
}
int64_t fun_rel = *(int32_t *)(data + pattern_offset - 0x04 ); // function call is just before our pattern
uint64_t patch_addr = (int64_t)data + pattern_offset + fun_rel;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_get_retire_timer,
(void **)&real_get_retire_timer);
}
/* hook commit option to flag hard gauge being selected */
{
/* find option commit function (unilab) */
if (config.game_version == 27)
{
int64_t pattern_offset = _search(data, dllSize, "\x89\x48\x0C\x8B\x56\x10\x89\x50\x10\x66\x8B\x4E\x14\x66\x89\x48\x14\x5B\xC3\xCC", 20, 0);
if (pattern_offset == -1) {
LOG("popnhax: survival gauge: cannot find option commit function (0)\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_survival_flag_hard_gauge,
(void **)&real_survival_flag_hard_gauge);
}
else if (config.game_version < 27)
{
/* wasn't found, look for older function */
int64_t first_loc = _search(data, dllSize, "\x0F\xB6\xC3\x03\xCF\x8D", 6, 0);
if (first_loc == -1) {
LOG("popnhax: survival gauge: cannot find option commit function (1)\n");
return false;
}
int64_t pattern_offset = _search(data, 0x50, "\x89\x50\x0C", 3, first_loc);
if (pattern_offset == -1) {
LOG("popnhax: survival gauge: cannot find option commit function (2)\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_survival_flag_hard_gauge_old,
(void **)&real_survival_flag_hard_gauge_old);
}
else
{
int64_t pattern_offset = _search(data, dllSize, "\x6B\xC9\x1A\x03\xC6\x8B\x74\x24\x10", 9, 0);
if (pattern_offset == -1) {
LOG("popnhax: survival gauge: cannot find option commit function (3)\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x14;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_survival_flag_hard_gauge_new,
(void **)&real_survival_flag_hard_gauge_new);
}
}
/* Fix medal calculation */
{
int64_t addr = _search(data, dllSize, "\x0F\xB7\x47\x12\x66\x83\xF8\x14", 8, 0);
if (addr == -1) {
LOG("popnhax: survival gauge: cannot find medal computation\n");
return false;
}
uint64_t function_addr = (int64_t)data + addr;
real_survival_gauge_medal_clear = (void (*)())function_addr;
int64_t pattern_offset = _search(data, dllSize, "\x0F\x9F\xC1\x5E\x8B\xD0\x3B\xC1\x7F\x02", 10, 0);
if (pattern_offset == -1) {
LOG("popnhax: survival gauge: cannot find medal computation hook\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x04;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_survival_gauge_medal,
(void **)&real_survival_gauge_medal);
}
/* gauge severity */
const char* severity_str[5] = { "", "COURSE (-35)", "NORMAL (-51)", "IIDX (-92)", "HARD (-204)"};
uint32_t severity_val[5] = { 0, 0xFFFFFFDD, 0xFFFFFFCD, 0xFFFFFFA4, 0xFFFFFF34};
if (severity != 1)
{
if (severity > 4)
severity = 4;
uint32_t decrease_amount = severity_val[severity];
char *as_hex = (char *) &decrease_amount;
if (!find_and_patch_hex(g_game_dll_fn, "\xB8\xDD\xFF\xFF\xFF", 5, 1, as_hex, 4))
{
if (!find_and_patch_hex(g_game_dll_fn, "\xBA\xDD\xFF\xFF\xFF", 5, 1, as_hex, 4))
{
LOG("\n");
LOG("popnhax: survival gauge: cannot patch severity\n");
}
}
}
if (!config.iidx_hard_gauge)
LOG("popnhax: survival_gauge debug: enabled (decrease rate : %s)\n", severity_str[severity]);
return true;
}
void (*real_survival_iidx_prepare_gauge)();
void hook_survival_iidx_prepare_gauge()
{
//this code is specific to survival gauge, so no additional check is required
__asm("cmp esi, 2\n");
__asm("jne skip_iidx_prepare_gauge\n");
__asm("shr eax, 1\n");
__asm("skip_iidx_prepare_gauge:\n");
real_survival_iidx_prepare_gauge();
}
void (*real_survival_iidx_apply_gauge)();
void hook_survival_iidx_apply_gauge()
{
__asm("cmp byte ptr %0, 0\n"::"m"(g_hard_gauge_selected));
__asm("je skip_iidx_apply_gauge\n"); //skip if not in survival gauge mode
__asm("cmp ecx, 2\n");
__asm("jb skip_iidx_apply_gauge\n"); //skip if gauge is not decreasing
__asm("mov ecx, 3\n");
__asm("cmp ax, 308\n");
__asm("jge skip_iidx_apply_gauge\n"); //skip if gauge is above 30%
__asm("mov ecx, 2\n");
__asm("skip_iidx_apply_gauge:\n");
real_survival_iidx_apply_gauge();
}
bool patch_survival_iidx()
{
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
/* put half the decrease value in the first slot */
{
int64_t pattern_offset = _search(data, dllSize, "\xE9\x8C\x00\x00\x00\x8B\xC6", 7, 0);
if (pattern_offset == -1) {
LOG("popnhax: iidx survival gauge: cannot find survival gauge prepare function\n");
return false;
}
int32_t delta = 0;
if (config.game_version > 27)
{
delta = 8;
}
uint64_t patch_addr = (int64_t)data + pattern_offset - delta;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_survival_iidx_prepare_gauge,
(void **)&real_survival_iidx_prepare_gauge);
}
/* switch slot depending on gauge value (get halved value when 30% or less) */
{
int64_t pattern_offset = _search(data, dllSize, "\x66\x83\xF8\x01\x75\x5E\x66\xA1", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: iidx survival gauge: cannot find survival gauge update function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x0C;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_survival_iidx_apply_gauge,
(void **)&real_survival_iidx_apply_gauge);
}
if (!config.iidx_hard_gauge)
LOG("popnhax: survival_gauge debug: IIDX-like <=30%% adjustment\n");
return true;
}
bool patch_survival_spicy()
{
if ( config.game_version <= 27)
{
if (!find_and_patch_hex(g_game_dll_fn, "\xB9\x02\x00\x00\x00\x66\x89\x0C\x75", 9, 1, "\x00\x00\x00\x00", 4))
{
LOG("\n");
LOG("popnhax: spicy survival gauge: cannot patch gauge increment\n");
return false;
}
}
else
{
if (!find_and_patch_hex(g_game_dll_fn, "\xBA\x02\x00\x00\x00\x66\x89\x14\x75", 9, 1, "\x00\x00\x00\x00", 4))
{
LOG("\n");
LOG("popnhax: spicy survival gauge: cannot patch gauge increment\n");
return false;
}
}
if (!config.iidx_hard_gauge)
LOG("popnhax: survival_gauge debug: spicy gauge\n");
return true;
}
void (*skip_convergence_value_get_score)();
void (*real_convergence_value_compute)();
void hook_convergence_value_compute()
{
__asm("push eax\n");
__asm("mov eax, dword ptr [eax]\n"); // music id (edi-0x38 or edi-0x34 depending on game)
__asm("cmp ax, 0xBB8\n"); // skip if music id is >= 3000 (cs_omni and user customs)
__asm("jae force_convergence_value\n");
__asm("pop eax\n");
__asm("jmp %0\n"::"m"(real_convergence_value_compute));
__asm("force_convergence_value:\n");
__asm("pop eax\n");
__asm("xor eax, eax\n");
__asm("jmp %0\n"::"m"(skip_convergence_value_get_score));
}
void (*skip_pp_list_elem)();
void (*real_pp_increment_compute)();
void hook_pp_increment_compute()
{
__asm("cmp ecx, 0xBB8\n"); // skip if music id is >= 3000 (cs_omni and user customs)
__asm("jb process_pp_elem\n");
__asm("jmp %0\n"::"m"(skip_pp_list_elem));
__asm("process_pp_elem:\n");
__asm("jmp %0\n"::"m"(real_pp_increment_compute));
}
static bool patch_db_fix_cursor(){
/* bypass song id sanitizer */
{
if (!find_and_patch_hex(g_game_dll_fn, "\x0F\xB7\x06\x66\x85\xC0\x7C\x1C", 8, -5, "\x90\x90\x90\x90\x90", 5))
{
LOG("popnhax: patch_db: cannot fix cursor\n");
return false;
}
}
/* skip 2nd check */
{
if (!find_and_patch_hex(g_game_dll_fn, "\x0F\xB7\x06\x66\x85\xC0\x7C\x1C", 8, 0x1A, "\xEB", 1))
{
LOG("popnhax: patch_db: cannot fix cursor (2)\n");
return false;
}
}
return true;
}
void (*real_pp_mean_compute)();
void hook_pp_mean_compute()
{
__asm("test ecx, ecx\n");
__asm("jnz divide_list\n");
__asm("mov eax, 0\n");
__asm("jmp skip_divide\n");
__asm("divide_list:\n");
__asm("div ecx\n");
__asm("skip_divide:\n");
real_pp_mean_compute();
}
void (*real_pp_convergence_loop)();
void hook_pp_convergence_loop()
{
__asm("movzx eax, word ptr[ebx]\n");
__asm("cmp eax, 0xBB8\n");
__asm("jl conv_loop_rearm\n");
__asm("mov al, 0\n");
__asm("jmp conv_loop_leave\n");
__asm("conv_loop_rearm:\n");
__asm("mov al, 1\n");
__asm("conv_loop_leave:\n");
real_pp_convergence_loop();
}
bool patch_db_power_points()
{
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
int64_t child_fun_loc = 0;
{
int64_t offset = _search(data, dllSize, "\x8D\x46\xFF\x83\xF8\x0A\x0F", 7, 0);
if (offset == -1) {
#if DEBUG == 1
LOG("popnhax: patch_db: failed to retrieve struct size and offset\n");
#endif
return false;
}
uint32_t child_fun_rel = *(uint32_t *) ((int64_t)data + offset - 0x04);
child_fun_loc = offset + child_fun_rel;
}
{
int64_t pattern_offset = _search(data, 0x40, "\x8d\x74\x01", 3, child_fun_loc);
if (pattern_offset == -1) {
LOG("popnhax: patch_db: failed to retrieve offset from base\n");
g_pfree_song_offset = 0x54; // best effort
return false;
}
g_pfree_song_offset = *(uint8_t *) ((int64_t)data + pattern_offset + 0x03);
}
/* skip cs_omni and customs in power point convergence value */
{
int64_t pattern_offset = _search(data, dllSize, "\x8B\x6C\x24\x30\x8B\x4C\x24\x2C", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: patch_db: cannot find power point convergence value computation loop\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset - 0x08;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_pp_convergence_loop,
(void **)&real_pp_convergence_loop);
}
/* make sure they cannot count (sanity check) */
{
int64_t pattern_offset = _search(data, dllSize, "\x84\xC0\x75\x11\x8D\x44\x24\x38", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: patch_db: cannot find convergence value computation\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x08;
skip_convergence_value_get_score = (void(*)()) (patch_addr + 0x05);
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_convergence_value_compute,
(void **)&real_convergence_value_compute);
}
/* skip cs_omni and customs in new stages pplist */
{
int64_t pattern_offset = _search(data, dllSize, "\x8A\x1E\x6A\x00\x51\xE8", 6, 0);
if (pattern_offset == -1) {
LOG("popnhax: patch_db: cannot find pp increment computation\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x02;
_MH_CreateHook((LPVOID)(patch_addr), (LPVOID)hook_pp_increment_compute,
(void **)&real_pp_increment_compute);
int64_t jump_addr_offset = _search(data, dllSize, "\x8B\x54\x24\x5C\x0F\xB6\x42\x0E\x45", 9, 0);
if (jump_addr_offset == -1) {
LOG("popnhax: patch_db: cannot find pp increment computation next iter\n");
return false;
}
skip_pp_list_elem = (void(*)()) ((int64_t)data + jump_addr_offset);
}
/* prevent crash when playing only customs in a credit */
{
if (!find_and_patch_hex(g_game_dll_fn, "\x0F\x8E\x5C\xFF\xFF\xFF\xEB\x04", 8, 6, "\x90\x90", 2))
{
LOG("popnhax: patch_db: cannot patch end list pointer\n");
}
}
/* prevent another crash when playing only customs in a credit (sanity check) */
{
int64_t pattern_offset = _search(data, dllSize, "\xC1\xF9\x02\x33\xD2\xF7\xF1\x8B\xC8", 9, 0);
if (pattern_offset == -1) {
LOG("popnhax: patch_db: cannot find power point mean computation\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x05;
patch_memory(patch_addr, (char*)"\x90\x90", 2); // erase original div ecx (is taken care of in hook_pp_mean_compute)
/* fix possible divide by zero error */
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)hook_pp_mean_compute,
(void **)&real_pp_mean_compute);
}
LOG("popnhax: patch_db: power point computation fixed\n");
return true;
}
static bool option_full()
{
/* patch default values in memory init function */
{
if (!find_and_patch_hex(g_game_dll_fn, "\x88\x48\x1A\x88\x48\x1B\x88\x48\x1C", 9, 0, "\xC7\x40\x1A\x00\x00\x01\x00\x90\x90", 9))
{
LOG("popnhax: cannot set full options by default\n");
return false;
}
}
LOG("popnhax: always display full options\n");
return true;
}
static bool option_guide_se_off(){
/* set guide SE OFF by default in all modes */
{
if ( config.game_version < 27 )
{
if (!find_and_patch_hex(g_game_dll_fn, "\x89\x48\x20\x88\x48\x24\xC3\xCC", 8, 3, "\xC6\x40\x24\x01\xC3", 5))
{
LOG("popnhax: guidese_off: cannot set guide SE off by default\n");
return false;
}
}
else if ( config.game_version == 27 )
{
if (!find_and_patch_hex(g_game_dll_fn, "\xC6\x40\x24\x01\x88\x48\x25", 7, 3, "\x00", 1))
{
LOG("popnhax: guidese_off: cannot set guide SE off by default\n");
return false;
}
}
else
{
if (!find_and_patch_hex(g_game_dll_fn, "\xC6\x40\x24\x01\xC6\x40\x25\x03", 8, 3, "\x00", 1))
{
LOG("popnhax: guidese_off: cannot set guide SE off by default\n");
return false;
}
}
}
LOG("popnhax: guidese_off: Guide SE OFF by default\n");
return true;
}
static bool option_net_ojama_off(){
/* set netvs ojama OFF by default */
{
if (!find_and_patch_hex(g_game_dll_fn, "\xC6\x40\xFD\x00\xC6\x00\x01", 7, 6, "\x00", 1))
{
LOG("popnhax: netvs_off: cannot set net ojama off by default\n");
return false;
}
}
LOG("popnhax: netvs_off: net ojama OFF by default\n");
return true;
}
uint8_t get_version()
{
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
{
int64_t pattern_offset = search(data, dllSize, "\x00\x8B\x56\x04\x0F\xB7\x02\xE8", 8, 0);
if (pattern_offset == -1) {
LOG("popnhax: get_version: cannot retrieve game version (eclale or less?)\n");
return 0;
}
uint8_t version = *(uint8_t*)(data + pattern_offset + 14);
return version;
}
}
static bool get_music_limit_from_file(const char *filepath, uint32_t *limit){
HANDLE hFile;
HANDLE hMap;
LPVOID lpBasePtr;
LARGE_INTEGER liFileSize;
hFile = CreateFile(filepath,
GENERIC_READ, // dwDesiredAccess
0, // dwShareMode
NULL, // lpSecurityAttributes
OPEN_EXISTING, // dwCreationDisposition
FILE_ATTRIBUTE_NORMAL, // dwFlagsAndAttributes
0); // hTemplateFile
if (hFile == INVALID_HANDLE_VALUE) {
//file not existing is actually a good thing
return false;
}
if (!GetFileSizeEx(hFile, &liFileSize)) {
LOG("popnhax: auto_diag: GetFileSize failed with error %ld\n", GetLastError());
CloseHandle(hFile);
return false;
}
if (liFileSize.QuadPart == 0) {
LOG("popnhax: auto_diag: popn22.dll file is empty?!\n");
CloseHandle(hFile);
return false;
}
hMap = CreateFileMapping(
hFile,
NULL, // Mapping attributes
PAGE_READONLY, // Protection flags
0, // MaximumSizeHigh
0, // MaximumSizeLow
NULL); // Name
if (hMap == 0) {
LOG("popnhax: auto_diag: CreateFileMapping failed with error %ld\n", GetLastError());
CloseHandle(hFile);
return false;
}
lpBasePtr = MapViewOfFile(
hMap,
FILE_MAP_READ, // dwDesiredAccess
0, // dwFileOffsetHigh
0, // dwFileOffsetLow
0); // dwNumberOfBytesToMap
if (lpBasePtr == NULL) {
LOG("popnhax: auto_diag: MapViewOfFile failed with error %ld\n", GetLastError());
CloseHandle(hMap);
CloseHandle(hFile);
return false;
}
char *data = (char *)lpBasePtr;
int32_t delta = 0;
//first retrieve .rdata virtual and raw addresses to compute delta
{
int64_t string_loc = _search(data, liFileSize.QuadPart, ".rdata", 6, 0);
if (string_loc == -1) {
LOG("popnhax: auto_diag: could not retrieve .rdata section header\n");
UnmapViewOfFile(lpBasePtr);
CloseHandle(hMap);
CloseHandle(hFile);
return false;
}
uint32_t virtual_address = *(uint32_t *)(data + string_loc + 0x0C);
uint32_t raw_address = *(uint32_t *)(data + string_loc + 0x14);
delta = virtual_address - raw_address;
}
//now attempt to find music limit from the dll
{
int64_t string_loc = _search(data, liFileSize.QuadPart, "Illegal music no %d", 19, 0);
if (string_loc == -1) {
LOG("popnhax: auto_diag: could not retrieve music limit error string\n");
UnmapViewOfFile(lpBasePtr);
CloseHandle(hMap);
CloseHandle(hFile);
return false;
}
string_loc += delta; //convert to virtual address
string_loc += 0x10000000; //entrypoint
char *as_hex = (char *) &string_loc;
int64_t pattern_offset = _search(data, liFileSize.QuadPart, as_hex, 4, 0);
if (pattern_offset == -1) {
LOG("popnhax: auto_diag: could not retrieve music limit test function\n");
UnmapViewOfFile(lpBasePtr);
CloseHandle(hMap);
CloseHandle(hFile);
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset - 0x1F;
*limit = *(uint32_t*)patch_addr;
}
UnmapViewOfFile(lpBasePtr);
CloseHandle(hMap);
CloseHandle(hFile);
return true;
}
bool g_timer_flipflop = 0;
void (*real_timer_increase)(void);
void hook_timer_increase()
{
__asm("cmp byte ptr [_g_timer_flipflop], 0\n");
__asm("je skip_cancel_increase\n");
__asm("dec dword ptr [ebp+0x44]\n");
__asm("dec byte ptr [_g_timer_flipflop]\n"); // 1 -> 0
__asm("jmp leave_timer_increase_hook\n");
__asm("skip_cancel_increase:\n");
__asm("inc byte ptr [_g_timer_flipflop]\n"); // 0 -> 1
__asm("leave_timer_increase_hook:\n");
real_timer_increase();
}
static bool patch_half_timer_speed()
{
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
{
int64_t pattern_offset = _search(data, dllSize, "\xFF\x45\x44\x3B\x75\x04", 6, 0);
if (pattern_offset == -1) {
LOG("popnhax: high_framerate: cannot find timer increase function\n");
return false;
}
uint64_t patch_addr = (int64_t)data + pattern_offset + 0x03;
_MH_CreateHook((LPVOID)patch_addr, (LPVOID)hook_timer_increase,
(void **)&real_timer_increase);
}
LOG("popnhax: halve timer speed\n");
return true;
}
static bool patch_afp_framerate(uint16_t fps)
{
DWORD framerate = fps;
if ( framerate == 0 )
{
DEVMODE lpDevMode;
memset(&lpDevMode, 0, sizeof(DEVMODE));
lpDevMode.dmSize = sizeof(DEVMODE);
lpDevMode.dmDriverExtra = 0;
if ( EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &lpDevMode) == 0 )
{
LOG("popnhax: high_framerate: could not retrieve display mode\n");
return false;
}
framerate = lpDevMode.dmDisplayFrequency;
} else {
LOG("popnhax: high_framerate: force %ldHz\n", framerate);
}
float new_value = 1./framerate;
char *as_hex = (char*)&new_value;
if ( !find_and_patch_hex(g_game_dll_fn, "\x82\x9D\x88\x3C", 4, 0, as_hex, 4) )
{
LOG("popnhax: high_framerate: cannot patch animation speed for %ldHz\n", framerate);
return false;
}
LOG("popnhax: high_framerate: patched animation speed for %ldHz\n", framerate);
return true;
}
static bool patch_quick_boot()
{
if ( !find_and_patch_hex(g_game_dll_fn, "\x8B\xF0\x8B\x16\x8B\x42\x04\x6A\x00", 9, -10, "\x90\x90\x90\x90\x90", 5) )
{
LOG("popnhax: quick_boot: cannot patch song checks\n");
return false;
}
LOG("popnhax: quick boot enabled\n");
return true;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
g_log_fp = fopen("popnhax.log", "w");
if (g_log_fp == NULL)
{
LOG("cannot open popnhax.log for write, output to stderr only\n");
}
LOG("== popnhax version " PROGRAM_VERSION " ==\n");
LOG("popnhax: Initializing\n");
if (MH_Initialize() != MH_OK) {
LOG("Failed to initialize minhook\n");
exit(1);
return TRUE;
}
bool force_trans_debug = false;
bool force_no_omni = false;
LPWSTR *szArglist;
int nArgs;
szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs);
if (szArglist == NULL) {
LOG("popnhax: Failed to get cmdline\n");
return 0;
}
for (int i = 0; i < nArgs; i++) {
/* game dll */
if ( wcsstr(szArglist[i], L".dll") != NULL && wcsstr(szArglist[i], L"popn2") == szArglist[i] )
{
char* resultStr = new char [wcslen(szArglist[i]) + 1];
wsprintfA ( resultStr, "%S", szArglist[i]);
g_game_dll_fn = strdup(resultStr);
}
/* config xml */
else if ( wcscmp(szArglist[i], L"--popnhax-config") == 0 )
{
char* resultStr = new char [wcslen(szArglist[i+1]) + 1];
wsprintfA ( resultStr, "%S", szArglist[i+1]);
g_config_fn = strdup(resultStr);
}
else if ( wcscmp(szArglist[i], L"--translation-debug") == 0 )
{
LOG("--translation-debug: turning on translation-related dumps\n");
force_trans_debug = true;
}
else if ( wcscmp(szArglist[i], L"--no-omni") == 0 )
{
LOG("--no-omni: force disable patch_db\n");
force_no_omni = true;
}
}
LocalFree(szArglist);
if (g_game_dll_fn == NULL)
g_game_dll_fn = strdup("popn22.dll");
uint8_t game_version = get_version();
LOG("popnhax: game dll: %s ",g_game_dll_fn);
if ( game_version != 0 )
LOG ("(popn%d)", game_version);
LOG("\n");
if (g_config_fn == NULL)
{
/* if there's an xml named like the custom game dll, it takes priority */
char *tmp_name = strdup(g_game_dll_fn);
strcpy(tmp_name+strlen(tmp_name)-3, "xml");
if (access(tmp_name, F_OK) == 0)
g_config_fn = strdup(tmp_name);
else
g_config_fn = strdup("popnhax.xml");
free(tmp_name);
}
LOG("popnhax: config file: %s\n",g_config_fn);
if ( !config_process(g_config_fn) )
{
LOG("FATAL ERROR: Could not pre-process config file\n");
exit(1);
}
strcpy(g_config_fn+strlen(g_config_fn)-3, "opt");
if (!_load_config(g_config_fn, &config, config_psmap))
{
LOG("popnhax: FATAL ERROR: failed to load %s. Running advanced diagnostic...\n", g_config_fn);
config_diag(g_config_fn, config_psmap);
exit(1);
}
config.game_version = game_version;
if ( config.extended_debug )
{
enable_extended_debug();
}
if ( strcmp(g_game_dll_fn, "popn22.dll") == 0 )
{
//ensure you're not running popn22.dll from the modules subfolder
char filename[MAX_PATH];
if ( GetModuleFileName(GetModuleHandle(g_game_dll_fn), filename, MAX_PATH+1) != 0 )
{
if ( strstr(filename, "\\modules\\popn22.dll") != NULL )
{
LOG("WARNING: running popn22.dll from \"modules\" subfolder is not recommended with popnhax. Please copy dlls back to contents folder\n");
}
} else {
LOG("WARNING: auto_diag: Cannot retrieve module path (%ld)\n", GetLastError());
}
//ensure there isn't a more recent version in modules subfolder
uint32_t modules_limit, current_limit;
if ( get_music_limit(&current_limit)
&& get_music_limit_from_file("modules\\popn22.dll", &modules_limit)
&& (modules_limit > current_limit) )
{
LOG("ERROR: a newer version of popn22.dll seems to be present in modules subfolder (%d vs %d songs). Please copy dlls back to contents folder\n", modules_limit, current_limit);
}
}
if (force_trans_debug)
config.translation_debug = true;
if (force_no_omni)
config.patch_db = false;
bool datecode_auto = (strcmp(config.force_datecode, "auto") == 0);
if (!config.disable_multiboot)
{
/* automatically force datecode based on dll name when applicable (e.g. popn22_2022061300.dll and force_datecode is empty or "auto") */
if ( (strlen(g_game_dll_fn) == 21)
&& ( datecode_auto || (config.force_datecode[0] == '\0') ) )
{
LOG("popnhax: multiboot autotune activated (custom game dll, no force_datecode)\n");
memcpy(config.force_datecode, g_game_dll_fn+7, 10);
LOG("popnhax: multiboot: auto set datecode to %s\n", config.force_datecode);
if (config.score_challenge && ( config.game_version < 26 || strcmp(config.force_datecode,"2020092800") <= 0 ) )
{
LOG("popnhax: multiboot: auto disable score challenge patch (already ingame)\n");
config.score_challenge = false;
}
if (config.patch_db && ( config.game_version == 0 || strcmp(config.force_datecode,"2016121400") < 0 ) )
{
LOG("popnhax: multiboot: auto disable omnimix patch (not compatible)\n");
config.patch_db = false;
}
if (config.guidese_off && ( config.game_version == 0 || strcmp(config.force_datecode,"2016121400") < 0 ) )
{
LOG("popnhax: multiboot: auto disable Guide SE patch (not compatible)\n");
config.guidese_off = false;
}
if (config.local_favorites && ( config.game_version == 0 || strcmp(config.force_datecode,"2016121400") < 0 ) )
{
LOG("popnhax: multiboot: auto disable local favorites patch (not compatible)\n");
config.local_favorites = false;
}
if ((config.tachi_scorehook || config.tachi_rivals) && ( config.game_version < 24 || strcmp(config.force_datecode,"2016120400") <= 0 ) )
{
LOG("popnhax: multiboot: auto disable tachi hooks (not supported)\n");
config.tachi_scorehook = false;
config.tachi_rivals = false;
}
}
}
if (config.force_datecode[0] != '\0')
{
if (!datecode_auto && strlen(config.force_datecode) != 10)
LOG("popnhax: force_datecode: Invalid datecode %s, should be 10 digits (e.g. 2022061300) or \"auto\"\n", config.force_datecode);
else
patch_datecode(config.force_datecode);
}
/* look for possible translation patch folder ("_yyyymmddrr_tr" for multiboot, or simply "_translation") */
if (!config.disable_translation)
{
char translation_folder[16] = "";
char translation_path[64] = "";
/* parse */
if ( g_datecode_override != NULL )
{
sprintf(translation_folder, "_%s%s", g_datecode_override, "_tr");
sprintf(translation_path, "%s%s", "data_mods\\", translation_folder);
if (access(translation_path, F_OK) != 0)
{
translation_folder[0] = '\0';
}
}
if (translation_folder[0] == '\0')
{
sprintf(translation_folder, "%s", "_translation");
sprintf(translation_path, "%s%s", "data_mods\\", translation_folder);
if (access(translation_path, F_OK) != 0)
{
translation_folder[0] = '\0';
}
}
if (translation_folder[0] != '\0')
{
LOG("popnhax: translation: using folder \"%s\"\n", translation_folder);
patch_translate(g_game_dll_fn, translation_folder, config.translation_debug);
}
else if ( config.translation_debug )
{
DWORD dllSize = 0;
char *data = getDllData(g_game_dll_fn, &dllSize);
LOG("popnhax: translation debug: no translation applied, dump prepatched dll\n");
FILE* dllrtp = fopen("dllruntime_prepatched.dll", "wb");
fwrite(data, 1, dllSize, dllrtp);
fclose(dllrtp);
}
}
/* ----------- r2nk226 ----------- */
if (config.practice_mode) {
version_check();
patch_practice_mode();
// record_mode must be usaneko or later
if (config.game_version >= 24) {
patch_record_mode(config.quick_retire);
}
#if DEBUG == 1
patch_hakc_errorskip();
#endif
/*
if(patch_silent_mode()) {
LOG("popnhax: silent mode patch enabled\n");
}
*/
}
/* --------------------------------- */
if (config.audio_source_fix) {
patch_audio_source_fix();
}
if (config.unset_volume) {
patch_unset_volume();
}
if (config.pfree) {
g_pfree_mode = true; /* used by asm hook */
patch_pfree();
}
if (config.quick_retire) {
patch_quick_retire(config.pfree);
} else if (config.back_to_song_select) {
LOG("WARNING: back to song select cannot be enabled without quick retire.\n");
}
if (config.score_challenge) {
patch_score_challenge();
}
if (config.force_hd_timing) {
patch_hd_timing();
}
if (config.force_hd_resolution) {
patch_hd_resolution(config.force_hd_resolution);
}
if (config.iidx_hard_gauge){
if (config.survival_gauge || config.survival_spicy || config.survival_iidx)
{
LOG("popnhax: iidx_hard_gauge cannot be used when other survival options are already set\n");
config.iidx_hard_gauge = false;
}
else
{
config.survival_gauge = 3;
config.survival_spicy = true;
config.survival_iidx = true;
}
}
if (config.survival_gauge) {
bool res = true;
res &= patch_hard_gauge_survival(config.survival_gauge);
if (config.survival_spicy) {
res &= patch_survival_spicy();
}
if (config.survival_iidx) {
res &= patch_survival_iidx();
}
if (config.iidx_hard_gauge && res)
LOG("popnhax: iidx_hard_gauge: HARD gauge is now IIDX-like\n");
}
if (config.hidden_is_offset){
patch_hidden_is_offset();
if (config.show_offset){
patch_show_hidden_adjust_result_screen();
}
}
if (config.show_fast_slow){
force_show_fast_slow();
}
if (config.show_details){
force_show_details_result();
}
if (config.audio_offset){
if (config.keysound_offset)
{
LOG("popnhax: audio_offset cannot be used when keysound_offset is already set\n");
}
else
{
config.disable_keysounds = true;
config.keysound_offset = -1*config.audio_offset;
LOG("popnhax: audio_offset: disable keysounds then offset timing by %d ms\n", config.keysound_offset);
}
}
if (config.disable_keysounds){
patch_disable_keysound();
}
if (config.base_offset){
patch_add_to_base_offset(config.base_offset);
}
if (config.keysound_offset){
/* must be called _after_ force_hd_timing and base_offset */
patch_keysound_offset(config.keysound_offset);
}
if (config.beam_brightness){
/* must be called _after_ force_hd_resolution */
patch_add_to_beam_brightness(config.beam_brightness);
}
if (config.event_mode) {
patch_event_mode();
}
if (config.remove_timer) {
patch_remove_timer();
}
if (config.freeze_timer) {
patch_freeze_timer();
}
if (config.skip_tutorials) {
patch_skip_tutorials();
}
if (config.patch_db) {
/* must be called after force_datecode */
LOG("popnhax: patching songdb\n");
if ( patch_database() )
{
patch_db_power_points();
patch_db_fix_cursor();
if (config.custom_categ)
patch_custom_categs(g_game_dll_fn, &config);
}
}
if (config.force_unlocks) {
if (!config.patch_db) {
/* Only unlock using these methods if it's not done directly through the database hooks */
force_unlock_songs();
force_unlock_charas();
}
patch_unlocks_offline();
force_unlock_deco_parts();
}
if (config.local_favorites)
{
if ( config.game_version == 0 )
{
LOG("popnhax: local_favorites: patch is not compatible with your game version.\n");
}
else
{
if ( strlen(config.local_favorites_path) > 0 )
{
while ( config.local_favorites_path[strlen(config.local_favorites_path)-1] == '\\' )
{
config.local_favorites_path[strlen(config.local_favorites_path)-1] = '\0';
}
if (access(config.local_favorites_path, F_OK) == 0)
LOG("popnhax: local_favorites: favorites are stored in %s\n", config.local_favorites_path);
else
{
LOG("WARNING: local_favorites: cannot access %s, defaulting to data_mods folder\n", config.local_favorites_path);
config.local_favorites_path[0] = '\0';
}
}
uint8_t *datecode = NULL;
if ( g_datecode_override != NULL )
{
datecode = (uint8_t*) strdup(g_datecode_override);
}
else
{
property *config_xml = load_prop_file("prop/ea3-config.xml");
READ_STR_OPT(config_xml, property_search(config_xml, NULL, "/ea3/soft"), "ext", datecode)
free(config_xml);
}
if (datecode == NULL) {
LOG("popnhax: local_favorites: failed to retrieve datecode.\n");
}
patch_local_favorites(g_game_dll_fn, config.game_version, config.local_favorites_path, ( config.game_version == 27 && datecode != NULL && strcmp((char*)datecode,"2023101700") >= 0 ) );
free(datecode);
}
}
if (config.force_full_opt)
option_full();
if (config.netvs_off)
option_net_ojama_off();
if (config.guidese_off)
option_guide_se_off();
if (config.high_framerate)
{
patch_afp_framerate(config.high_framerate_fps);
patch_half_timer_speed();
config.fps_uncap = true;
} else if (config.force_slow_timer) {
patch_half_timer_speed();
}
if (config.fps_uncap)
patch_fps_uncap(config.high_framerate_limiter ? config.high_framerate_fps : 0);
if (config.enhanced_polling)
{
// setup hardware offload if applicable
g_hardware_offload = patch_enhanced_polling_hardware_setup();
patch_enhanced_polling(config.debounce, config.enhanced_polling_stats);
if (config.enhanced_polling_stats)
{
patch_enhanced_polling_stats();
}
if (g_hardware_offload)
patch_usbio_string();
}
if (config.hispeed_auto)
{
g_default_bpm = config.hispeed_default_bpm;
patch_hispeed_auto(config.hispeed_auto);
}
if (config.practice_mode && config.tachi_scorehook)
{
LOG("WARNING: tachi: scorehook not compatible with practice mode, disabling it.\n");
config.tachi_scorehook = false;
}
if (config.tachi_scorehook || config.tachi_rivals)
{
SearchFile s;
bool found = false;
s.search(".", "conf", false);
auto result = s.getResult();
if ( result.size() > 0 )
{
for (uint16_t i=0; i<result.size(); i++) {
if (strstr(result[i].c_str(), ".\\_tachi.") == result[i].c_str())
{
found = true;
break;
}
}
}
if (!found)
{
LOG("WARNING: tachi: no config file found for tachi hooks ( _tachi.default.conf or _tachi.<friendid>.conf ), disabling them.\n");
config.tachi_scorehook = false;
config.tachi_rivals = false;
}
}
if (config.tachi_scorehook)
{
patch_tachi_scorehook(g_game_dll_fn, config.pfree, config.hidden_is_offset, config.tachi_scorehook_skip_omni);
}
if (config.tachi_rivals)
{
//must be called after scorehook
patch_tachi_rivals(g_game_dll_fn, config.tachi_scorehook);
}
if (config.autopin)
{
patch_autopin();
}
if (config.attract_interactive)
{
patch_attract_interactive();
}
if (config.attract_lights)
{
patch_attract_lights();
}
if (config.attract_ex)
{
patch_ex_attract( config.hispeed_auto ? config.hispeed_default_bpm : 0 );
}
if (config.attract_full)
{
patch_full_attract();
}
if (config.quick_boot)
{
patch_quick_boot();
}
if (config.time_rate)
patch_get_time(config.time_rate/100.);
MH_EnableHook(MH_ALL_HOOKS);
LOG("popnhax: done patching game, enjoy!\n");
if (g_log_fp != stderr)
fclose(g_log_fp);
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}