You are not logged in.
The Wave Scanner is an accessory that connects to the DS through the microphone connector used by one game(technically 3), Ryuusei no Rockman. It has minigames that can be played on it to level it up. That level can be transferred to the DS to power up your character. It can also scan "battle cards", which have barcodes on them, and send them to the DS. Its operation is simple and requires no emulation changes as long as the wav samples are encoded at the exact right bitrate(that's a discussion for another topic).
I've written a Python script that will generate wav samples for every card and a variety of Wave Scanner devices.
https://gist.github.com/windwakr/058a78 … a0b950771c
The wavs are directly usable in DeSmuME as-is. Enjoy.
Demonstration video. I power up with a level 50 Pegasus Wave Scanner then enter battle and scan an Air Spreader card.
https://webmshare.com/play/ZJB6w
-----
This one is just partially understood.
The Direction Sensing Card is the(official? unofficial?) name for a special oversized slot-1 game card used by the game Hoshizora Navi. The game communicates with the built-in AK8973 compass chip over game card SPI commands. I haven't seen the internals of one of these game cards so I'm not sure how it's wired up.
The games uses these SPI commands
0x10 - ??
0x11 - ??
0x2X - Write, the lower nibble indicates the amount of bytes
0x4X - ??
0x60 - Read byte
0x80 - ?? Game halts until it's 0 when reading
0x82 - ?? Game halts until it's 0 when reading
0x85 - ?? Game writes 0x98 here
0x88 - Seems to be used to set the reset pin on the compass chip?
Using just the 0x2X, 0x60, and 0x8X commands you can emulate enough of the chip to get the game to successfully read/write values.
The game also has an accelerometer chip. Again, as I haven't seen inside the game card I'm unsure of what the exact chip is. It writes to registers 0x20-0x22, checks the status of register 0x27, then reads registers 0x28-0x2D for the accelerometer outputs. Turns out there's dozens of chips with this exact configuration. I've been referring to the datasheet for LIS3DH, although it's highly unlikely that's the exact chip inside. The game reads from the accelerometer through spi 02 commands. It writes the (register number | 0xC0) then reads the result. On successive reads the next register is read. I haven't traced how register writes are performed. I'm also unsure how these commands are differentiated from BM_CMD_WRITELOW commands.
What's the point of emulating this functionality when the game already has a freelook mode using the dpad? It would be useful for Android emulation, I suppose.
For future reference this may be a good base for generating realistic-looking magnetometer output
https://github.com/NavPy/SensorSim/blob … sim_mag.py
It would still need to be shaped into whatever the AK8973 outputs(with its offset/gain stuff).
I'd imagine a stationary rotating accelerometer wouldn't be too difficult to simulate the output of.
Here's a video demonstration with very poorly coded inputs. The first half is spent "calibrating" the device, which just consists of rotating it around a bit until the compass icon turns green.
https://webmshare.com/play/oOvY6
Try using the "Load state from" option and selecting your backup state in there. You should also be aware that you shouldn't really be using savestates with the Pokemon games, as they tend to break over time. Make regular in-game saves and maybe even back up your battery folder every now and then.
Wave sample handling on Windows is completely busted. It doesn't take into account either the samplerate of the input wave file or the sampling rate the game is performing. Most wave samples will be fed to the game way too slowly/quickly and not have the intended effect. I've got some sample code to try and fix this which is in the archive at the end of the post. It's not quite polished enough to submit a PR, more of a proof of concept. Various things like the savestate code would need modification and I cba. I'm also not sure if nds_arm7_timer is the value I should be using for "overall arm7 clock cycles" but I couldn't see anything else keeping track of it when looking around.
Those changes ALMOST let Bangai-O Spirits start reading level data. Almost. The game samples the mic at roughly ~32.7kHz, checking for a 1024Hz sine wave. What I've found from debugging is that DeSmuME sometimes has a massive gap in cycle times from sample to sample. Tens of thousands. This leads to entire bits of level data being skipped, breaking the whole transfer. My somewhat hacky solution is to log the first 21(arbitrarily chosen) sample cycle differences when the user presses the microphone button. It then takes the median value to discard outliers and uses that as the mic sampling frequency until the user releases the microphone button.
Here's a demonstration of that. I added in a volume slider too, because the game is quite picky about that:
https://files.catbox.moe/i3p5jv.webm
Level audio files, DeSmuME binary, source changes:
https://files.catbox.moe/fa5ofy.zip
Probably the most uselss add-on.
The Bey Point Reader(BPR) is an accessory used by some Japanese-only Beyblade games to read the data off of a device called a Beypointer. It can also send your name to the Beypointer. What is a Beypointer? I have no clue.
Minimal code for detection is returning 0x96 on byte-sized reads and 0xF500 on word-sized reads from 0x080000BE.
The BPR uses these addresses for communication, all word-sized.
0x0815FE00, 0x0815FE02..0x0815FE0E : Buffer for communication packets
0x0815FE10 : Status/Config?
0x0815FE12 : Size of packet in buffer, written from DS
0x0815FE14 : Size of packet in buffer, written from BPR
0x0815FE16 : ???
0x0815FEFE : Read from before every read/write, discarded. Clocks the Beypointer?
pic: https://files.catbox.moe/uxx5lv.png
save file for Metal Fight Beyblade (J): https://files.catbox.moe/f1qhju.zip
To access the BPR feature, バトル -> たいせんメニュー -> BPコラしん
Here is minimal code for working read/write, though there's no persistent storage after closing the emulator.
This test code was written in place of the guitar grip code out of laziness.
/*
Copyright (C) 2009 CrazyMax
Copyright (C) 2009-2013 DeSmuME team
This file is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with the this software. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../slot2.h"
//#include "../armcpu.h"
#define ARR_SIZE 0x30 //IDK. Game never reads higher than around 0x2D
//0-2 = some points, 3-5 = some other points, 6-7 = wins, 8-9 = losses, 0xA-0xB = "game overs", 0xC = battle something(can't read it)
// 0x18-0x27 = 8 character name in Shift-JIS, only supports some characters
//little endian
static u8 arr[ARR_SIZE] = { 0xA8, 0x16, 0x03, 0xF0, 0x06, 0x00, 0x55, 0xA4, 0x5C, 0x11, 0x00, 0x08, 0x37, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x82, 0x60, 0x82, 0x61, 0x82, 0x62, 0x82, 0x63,
0x82, 0x64, 0x82, 0x65, 0x00, 0x00, 0x00, 0x00, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,};
class Slot2_GuitarGrip : public ISlot2Interface
{
private:
int processCommand;
u16 status; //idk
u8 buf[8] = { 0 };
u8 bufSize = 0;
u8 reverseNibble(u8 val) { return (val & 0xF0) >> 4 | (val & 0x0F) << 4; }
u16 processBpr(u32 addr)
{
u16 outWord = 0;
u8 pos;
u8 size;
if (processCommand) {
processCommand = 0;
u8 cmd = buf[0];
switch (cmd) {
case 0x40: //?
break;
case 0x44: //Read
pos = reverseNibble(buf[1]);
size = reverseNibble(buf[2]);
for (int i = 0; i < size; i++) { //Copy data into buf
if ((pos+i < ARR_SIZE) && (i+1 < 8))
buf[i + 1] = reverseNibble(arr[pos + i]);
}
buf[0] = 0x46;
bufSize = size + 1;
status = 0x02;
printf("BPR READ, POS: %02X, SIZE: %02X\n", pos, size);
break;
case 0x45: //Write
pos = reverseNibble(buf[1]);
size = reverseNibble(buf[2]);
for (int i = 0; i < size; i++) { //Copy data from buf
if ((pos+i < ARR_SIZE) && (i+3 < 8))
arr[pos+i] = reverseNibble(buf[i + 3]);
}
buf[0] = 0x46; //Only need to change the first byte of the buffer, the rest is fine
bufSize = size + 3;
status = 0x02;
printf("BPR WRITE, POS: %02X, SIZE: %02X\n", pos, size);
break;
case 0x46: //Data
buf[0] = 0x4C;
bufSize = 1;
status = 0x02;
break;
case 0x4C: //?
status = 0x04;
break;
default:
outWord = 0;
break;
}
}
if ((addr >= 0x0815FE00) && (addr <= 0x815FE0E)) {
u32 tmp = (addr & 0xF) / 2;
outWord = buf[tmp];
} else if (addr == 0x0815FE10) {
outWord = status;
}else if (addr == 0x0815FE14) {
outWord = bufSize;
}
return outWord;
}
public:
virtual Slot2Info const* info()
{
//static Slot2InfoSimple info("Guitar Grip", "Guitar Grip for Guitar Hero games", 0x04);
static Slot2InfoSimple info("Bey Point Reader", "BPR", 0x04);
return &info;
}
virtual void connect()
{
processCommand = 0;
bufSize = 0;
status = 0;
}
virtual void writeWord(u8 PROCNUM, u32 addr, u16 val)
{
//printf("writeWord: %08X = %04X\n", addr, val);
if ((addr >= 0x0815FE00) && (addr <= 0x0815FE0E)) {
u32 tmp = (addr & 0xF) / 2;
buf[tmp] = val & 0xFF;
}
if ((addr == 0x0815FE12)) {
bufSize = val & 0xFF;
processCommand = 1;
} else if (addr == 0x0815FE16) {
status = 0x0; //?
}
}
virtual u8 readByte(u8 PROCNUM, u32 addr)
{
//if (addr != 0x080000B2)
// printf("readByte: %08X\n", addr);
return 0x96; //Detection
}
virtual u16 readWord(u8 PROCNUM, u32 addr)
{
u16 outWord = 0x0000;
if (addr == 0x080000BE) { //Detection
outWord = 0xF500;
}
if ((addr >= 0x815FE00) && (addr < 0x815FEFE))
outWord = processBpr(addr);
//if ((addr > 0x080000BE) && (addr != 0x0815FEFE))
// printf("readWord: %08X = %04X\n", addr, outWord);// from %08X(ARM%c)\n", addr, outWord, ARMPROC.instruct_adr, PROCNUM ? '7' : '9');
return outWord;
}
virtual u32 readLong(u8 PROCNUM, u32 addr) { if (addr != 0x080000AC) printf("readLong: %08X\n", addr); return 0x0; }
};
ISlot2Interface* construct_Slot2_GuitarGrip() { return new Slot2_GuitarGrip(); }
void guitarGrip_setKey(bool green, bool red, bool yellow, bool blue)
{
//const u8 g = (green) ? (1 << 6) : 0;
//const u8 r = (red) ? (1 << 5) : 0;
//const u8 y = (yellow) ? (1 << 4) : 0;
//const u8 b = (blue) ? (1 << 3) : 0;
//
//guitarKeyStatus = ~(g | r | y | b);
}
void guitarGrip_setKey(u8 theKeys)
{
//guitarKeyStatus = theKeys;
}
I have no desire to properly implement these so I'll just leave this info here, where it is unlikely to be seen by anyone.
The Motion Pack was used by one game, Tony Hawk's Motion. The bare minimum for detection to succeed is to return 0xFCXX on word sized reads. The game will enter a loop polling bytes from 0x0A000000. I don't fully understand this part but you can get it to continue by letting it read [0x00, 0x03, 0x03, 0x03, 0x03]. It will then read bytes from the same address for the X, Y, and Z acceleration values. These are served two bits at a time(starting from the top of the byte) in the least significant bits of the return value. for a total of 12 reads. A value of 0x80 seems to be the neutral point for each acceleration value? It will then go back to polling the address for those 5 values from earlier. This is enough to fully play Tony Hawk and its included "Hue Pixel Painter" minigame. In my example video I had hooked up Lua bindings and sent in my controller analog stick as the X/Y acceleration values through a Lua script.
One additional note is that depending on the values received, the game can change how it accesses the device. If you only send 0x03 on byte reads initially then the Tony Hawk portion of the game will actually switch over to reading it as if it were an NDSmotion Motion Pak. See libnds for how that works: https://github.com/devkitPro/libnds/blo … dsmotion.c
This mode is not necessary to emulate for playing the game as you can just send the sequence I mentioned earlier instead to have it read the values sequentially from 0x0A000000.
Video: https://files.catbox.moe/wkpnb6.webm
The Bayer Didget was used by one game, Knock 'Em Downs: World's Fair. The bare minimum for detection to succeed is to return 0xF3XX on word sized reads. The game then performs two reads from 0x0A000000 and expects them to be 0xBD and 0xDA. It then works like a glucoboy, which shonumi has reverse engineered and written a blog post about. After it writes to 0x0A000000 to pick a register to read, you trigger a gba slot interrupt( setIF(PROCNUM, 0x2000); ) to tell it that a byte is ready for it to read from 0x0A000000. I'm not sure if the registers it reads from are exactly the same as the glucoboy, so it would take a bit of exploration. Overall though, it's a simple add-on to emulate.
Here is every barcode supported by Mushi King, which I extracted from the game.
https://windwakr.github.io/mushikingds.html
Here are barcodes for Oshare Majo.
https://windwakr.github.io/loveandberry.html
And here are the codes for the last HCV-1000 game, Card de Asobu.
https://windwakr.github.io/cardasobu.html
EDIT:
DeSmuME now supports the Sega Card Reader (HCV-1000) in the latest nightly builds(Windows only).
Set the GBA slot to the Sega Card Reader then set up a hotkey and the barcode.
hey windwakr can you create another lua script that has the ability to move the camera using keyboard?
https://files.catbox.moe/9gsn8r.webm
This adds keyboard support and mouse aiming. No thought was put into the controls other than avoiding DeSmuME's default hotkeys, so feel free to adjust them.
--Configure the keyboard controls below
forwardsKey = "U"; --These keys move the camera forward, backward, left, right, up, down
backwardsKey = "J"; --^^^
leftKey = "H"; --^^^
rightKey = "K"; --^^^
upKey = "T"; --^^^
downKey = "G"; --^^^
rotateUpKey = "numpad8"; --These keys rotate the camera
rotateDownKey = "numpad5"; --^^^
rotateLeftKey = "numpad4"; --^^^
rotateRightKey = "numpad6"; --^^^
pitchOffsetKey = "Y"; --Sets the pitch offset to correct for unwanted roll. Aim at the horizon and press to set it.
resetKey = "L"; --Resets everything
movScaleDownKey = "numpad0"; --Adjusts movement scaling down
movScaleUpKey = "numpad1"; --Adjusts movement scaling up
rotScaleDownKey = "numpad2"; --Adjusts rotation scaling down
rotScaleUpKey = "numpad3"; --Adjusts rotation scaling up
scaleMultDownKey = "numpad7"; --Makes the scale adjustment keys 10x less effective
scaleMultUpKey = "numpad9"; --Makes the scale adjustment keys 10x more effective
mouseAimButton = "rightclick"; --Button for mouse aiming. May be leftclick, rightclick, middleclick or a keyboard key
--Gamepad controls assume you have an Xbox style controller
-- Left stick moves left/right and forward/back
-- Right stick rotates
-- Triggers move up down
--
-- A button resets position, rotation, and pitch offset
-- B button resets rotation and pitch offset
-- X sets pitch offset
-- Y resets the movement and rotation scale
--
-- Dpad Up/Down adjusts the movement scale
-- Dpad Left/Right adjusts the rotation scale
-- Hold the left or right bumpers to make smaller or bigger changes to the scales
--
-- Setting the pitch offset is important. Without doing so, your view will roll around as you look left and right in most games.
-- Angle your view towards the "horizon" and rotate the camera 90 degrees to the left or right. Then hold X and adjust up/down a little until everything's level.
joyID = -1; --Controller ID to use. The script will attempt to find a suitable controller, but you can manually set this if you wish
yaw = 0.0;
pitch = 0.0;
pitchoffs = 0.0; --used to correct for the initial pitch of the camera
xpos = 0.0; --coordinates in a virtual 3D space
ypos = 0.0; --^^^
zpos = 0.0; --^^^
matAdjustment = {1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1};
movscale = 1.0;
rotscale = 1.0;
cooldown = 0;
function MatrixMult4x4(m1, m2) --sorta copied/inspired from some lua matrix library
local mtx = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
for i=1,4 do
for j=1,4 do
local num = 0
for n=1,4 do
num = num + m1[(i-1)*4+n] * m2[(n-1)*4+j]
mtx[(i-1)*4+j] = num
end
end
end
return mtx;
end
function Rotate(angle,x,y,z)
local s = math.sin(math.rad(angle));
local c = math.cos(math.rad(angle));
return {(1-c)*x*x+c, (1-c)*y*x-s*z, (1-c)*z*x+s*y, 0,
(1-c)*x*y+s*z, (1-c)*y*y+c, (1-c)*z*y-s*x, 0,
(1-c)*x*z-s*y, (1-c)*y*z+s*x, (1-c)*z*z+c, 0,
0, 0, 0, 1};
end
function vecrotx(angle, vx, vy, vz) --used for pitch correction in the functions below
return vx,
vy * math.cos(math.rad(angle)) - vz * math.sin(math.rad(angle)),
vy * math.sin(math.rad(angle)) + vz * math.cos(math.rad(angle));
end
function ApplyMovement(fX, fY, fZ, l)
fX, fY, fZ = vecrotx(pitchoffs, fX, fY, fZ);
local mag = math.sqrt(fX*fX + fY*fY + fZ*fZ);
fX = fX / mag; fY = fY / mag; fZ = fZ / mag;
fX = fX * l; fY = fY * l; fZ = fZ * l;
xpos = xpos + fX; ypos = ypos + fY; zpos = zpos + fZ;
end
function MoveForward(l) --sorta based off https://www.gamedev.net/forums/topic/415144-get-forward-right-up-vectors-from-pitch-yaw-roll/
local fX = math.sin(math.rad(yaw)) * math.cos(math.rad(pitch+pitchoffs));
local fY = -math.sin(math.rad(pitch+pitchoffs));
local fZ = -math.cos(math.rad(yaw)) * math.cos(math.rad(pitch+pitchoffs));
ApplyMovement(fX, fY, fZ, l);
end
function MoveUp(l)
local fX = math.sin(math.rad(yaw)) * math.cos(math.rad(pitchoffs+90));
local fY = -math.sin(math.rad(pitchoffs+90));
local fZ = -math.cos(math.rad(yaw)) * math.cos(math.rad(pitchoffs+90));
ApplyMovement(fX, fY, fZ, l);
end
function MoveRight(l)
local fX = math.sin(math.rad(yaw+90));
local fY = 0;
local fZ = -math.cos(math.rad(yaw+90));
ApplyMovement(fX, fY, fZ, l);
end
function on3d()
emu.set3dtransform(2,matAdjustment);
end
emu.register3devent(on3d);
emu.set3dtransform(1,nil);
if joyID == -1 then
for i = 0, 15 do
c = controller.get(i);
if c.x and c.y and c.z and c.u and c.r then --simple check that only looks for all the needed axes
joyID = i;
print("Found suitable controller, id " .. i);
break;
end
end
else
print("Using manually set controller, id " .. joyID);
end
if joyID == -1 then
print("No suitable controller found!");
end
while true do
if joyID ~= -1 then
local joy = controller.get(joyID)
scalemult = 1;
if joy["5"] then --Left bumper
scalemult = 0.1;
elseif joy["6"] then --Right bumper
scalemult = 10;
end
if joy.up and cooldown==0 then --Dpad
cooldown = 10;
movscale = movscale + (0.1 * scalemult);
print("Movement scale: " .. movscale);
end
if joy.down and cooldown==0 then --Dpad
cooldown = 10;
movscale = movscale - (0.1 * scalemult);
print("Movement scale: " .. movscale);
end
if joy.right and cooldown==0 then --Dpad
cooldown = 10;
rotscale = rotscale + (0.1 * scalemult);
print("Rotation scale: " .. rotscale);
end
if joy.left and cooldown==0 then --Dpad
cooldown = 10;
rotscale = rotscale - (0.1 * scalemult);
print("Rotation scale: " .. rotscale);
end
if(math.abs(joy.x)>0.25) then --Left Stick
MoveRight(-joy.x * movscale);
end
if(math.abs(joy.y)>0.25) then --Left Stick
MoveForward(joy.y * movscale);
end
if(math.abs(joy.z)>0.25) then --Triggers
MoveUp(-joy.z * movscale);
end
if(math.abs(joy.u)>0.25) then --Right Stick
yaw = (yaw + (joy.u * rotscale)) % 360;
end
if(math.abs(joy.r)>0.25) then --Right Stick
pitch = (pitch + (joy.r * rotscale)) % 360;
end
if joy["1"] and cooldown==0 then --A button
cooldown = 20;
yaw = 0.0;
pitch = 0.0;
pitchoffs = 0.0;
xpos = 0.0;
ypos = 0.0;
zpos = 0.0;
movscale = 1.0;
rotscale = 1.0;
print("Reset everything");
end
if joy["2"] and cooldown==0 then --B button
cooldown = 20;
yaw = 0.0;
pitch = 0.0;
pitchoffs = 0.0;
print("Reset rotations");
end
if joy["3"] then --X button
pitchoffs = -pitch;
end
if joy["4"] and cooldown==0 then --Y button
cooldown = 20;
movscale = 1.0;
rotscale = 1.0;
print("Movement/rotation scales reset");
end
if joy["8"] and cooldown==0 then --Start
cooldown = 20;
print(string.format("X,Y,Z = {%.4f, %.4f, %.4f}", xpos, ypos, zpos));
print(string.format("yaw,pitch = {%.4f, %.4f}", yaw, pitch));
print(string.format("pitch offset = %.4f", pitchoffs));
end
end
--Ugly duplicated code for keyboard input
keys = input.get()
scalemult = 1;
if keys[scaleMultDownKey] then
scalemult = 0.1;
elseif keys[scaleMultUpKey] then
scalemult = 10;
end
if keys[movScaleUpKey] and cooldown==0 then --Dpad
cooldown = 10;
movscale = movscale + (0.1 * scalemult);
print("Movement scale: " .. movscale);
end
if keys[movScaleDownKey] and cooldown==0 then --Dpad
cooldown = 10;
movscale = movscale - (0.1 * scalemult);
print("Movement scale: " .. movscale);
end
if keys[rotScaleUpKey] and cooldown==0 then --Dpad
cooldown = 10;
rotscale = rotscale + (0.1 * scalemult);
print("Rotation scale: " .. rotscale);
end
if keys[rotScaleDownKey] and cooldown==0 then --Dpad
cooldown = 10;
rotscale = rotscale - (0.1 * scalemult);
print("Rotation scale: " .. rotscale);
end
if keys[forwardsKey] then
MoveForward(-1 * movscale);
end
if keys[backwardsKey] then
MoveForward(1 * movscale);
end
if keys[leftKey] then
MoveRight(1 * movscale);
end
if keys[rightKey] then
MoveRight(-1 * movscale);
end
if keys[upKey] then
MoveUp(1 * movscale);
end
if keys[downKey] then
MoveUp(-1 * movscale);
end
if keys[rotateUpKey] then
pitch = (pitch - 1 * rotscale) % 360;
end
if keys[rotateDownKey] then
pitch = (pitch + 1 * rotscale) % 360;
end
if keys[rotateLeftKey] then
yaw = (yaw - 1 * rotscale) % 360;
end
if keys[rotateRightKey] then
yaw = (yaw + 1 * rotscale) % 360;
end
if keys[pitchOffsetKey] then
pitchoffs = -pitch;
end
if keys[resetKey] and cooldown==0 then
cooldown = 20;
yaw = 0.0;
pitch = 0.0;
pitchoffs = 0.0;
xpos = 0.0;
ypos = 0.0;
zpos = 0.0;
movscale = 1.0;
rotscale = 1.0;
print("Reset everything");
end
--Mouse aiming
if keys[mouseAimButton] then
if keys.xmouse and keys.ymouse then
tmpX = (keys.xmouse - 128) / 128;
tmpY = (keys.ymouse - 96) / 96;
yaw = (yaw + tmpX * rotscale) % 360;
pitch = (pitch + tmpY * rotscale) % 360;
end
end
local tmp = Rotate(-yaw, 0.0, math.cos(math.rad(pitchoffs)), math.sin(math.rad(pitchoffs))); --roll correction magic sauce
tmp = MatrixMult4x4(tmp, Rotate(-pitch, 1.0, 0.0, 0.0));
local translation = {1,0,0,0,0,1,0,0,0,0,1,0,xpos,ypos,zpos,1};
matAdjustment = MatrixMult4x4(translation, tmp);
if cooldown>0 then cooldown=cooldown-1 end
emu.frameadvance();
end
Somebody please help me, I can't for the life of me get this working, as soon as I start the lua script the controls go crazy, the camera starts spinning in circles and I can't do anything. I'm using the latest desmume and the dll files are there, i'm using DS4 Windows and a ps4 controller which is recognized as a xbox 360 controller. I'm really not sure what im doing wrong and I have honestly spent most of this day looking into this to no avail. Please help
Try adjusting the joyid number at the top of the script and see if that helps. It accepts values 0 to 15.
This version of the script should work with US Rev 1. I have not extensively tested it, though.
joyid = 0 --change this if your preferred controller isn't being picked up
deadzone = 0.15 --Adjust analog stick deadzones
cooldown=0
weapon=0
subweapon=0
togglestylus=false
toggleaim=false
vcurx=128
vcury=96
key = {}
joy = {}
--Set the controls here
morphkey = "joy.down" --morph ball switch
visorkey = "joy.up" --scan visor switch(hold)
okkey = "joy.2" --ok button in messages
leftkey = "joy.left" --left arrow in messages
rightkey = "joy.right" --right arrow in messages
weaponkey = "joy.4" --weapon cycle
subweaponkey1 = "joy.5" --subweapon cycle back
subweaponkey2 = "joy.6" --subweapon cycle forward
leftstickx = "joy.x" --must be analog stick axis
leftsticky = "joy.y" --must be analog stick axis
rightstickx = "joy.u" --must be analog stick axis
rightsticky = "joy.r" --must be analog stick axis
shootkey = "joy.z" --may be either analog axis or button
boostkey = "joy.3" --morph ball boost
vstyluskey = "joy.7" --virtual stylus(hold)
jumpkey = "joy.1" --jump
startkey = "joy.8" --start
--https://www.lua.org/pil/14.1.html
function getfield (f)
local v = _G -- start with the table of globals
for w in string.gfind(f, "[%w_]+") do
v = v[w]
end
return v
end
while true do
joysend = {}
key=input.get()
joy = controller.get(joyid)
if getfield(vstyluskey) then
toggleaim = true --this exists to just delay the pressing of the screen when you release the virtual stylus key
if vcury>0 then
gui.drawbox(vcurx-1, vcury-1, vcurx+1, vcury+1, "#000000", "#FFFFFF")
else
gui.drawbox(vcurx-1, vcury, vcurx+1, vcury+1, "#FFFFFF") --workaround for vcury=0, as it would draw to the top screen
gui.pixel(vcurx, vcury, "#000000")
end
if getfield(subweaponkey2) then
stylus.set{x=vcurx, y=vcury, touch=true}
end
if getfield(subweaponkey1) and cooldown==0 then
togglestylus = not togglestylus
print('Touchscreen input allowed = ' .. tostring(togglestylus))
cooldown = 20
end
tmp = getfield(rightstickx)
if tmp and math.abs(tmp)>deadzone then
vcurx = vcurx + (tmp * 5)
end
tmp = getfield(rightsticky)
if tmp and math.abs(tmp)>deadzone then
vcury = vcury + (tmp * 5)
end
if vcurx<0 then vcurx=0 end
if vcurx>255 then vcurx=255 end
if vcury<0 then vcury=0 end
if vcury>191 then vcury=191 end
else
if getfield(morphkey) then --Morph
stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
stylus.set{x=231, y=167, touch=true} emu.frameadvance() emu.frameadvance()
end
if getfield(visorkey) then --Visor
if cooldown == 0 then
stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
end
cooldown = 10
stylus.set{x=128, y=173, touch=true}
end
if getfield(okkey) then --OK (in scans and messages)
stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
stylus.set{x=128, y=142, touch=true} emu.frameadvance() emu.frameadvance()
end
if getfield(leftkey) then --Left arrow (in scans and messages)
stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
stylus.set{x=71, y=141, touch=true} emu.frameadvance() emu.frameadvance()
end
if getfield(rightkey) then --Right arrow (in scans and messages)
stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
stylus.set{x=185, y=141, touch=true} emu.frameadvance() emu.frameadvance()
end
if getfield(weaponkey) and cooldown==0 then --Switch weapon (beam->missile->subweapon->beam)
cooldown=20
weapon=(weapon+1)%3
stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
stylus.set{x=85+40*weapon, y=32, touch=true} emu.frameadvance()
stylus.set{x=85+40*weapon, y=32, touch=true}
end
if (getfield(subweaponkey1) or getfield(subweaponkey2)) and cooldown==0 then --Switch subweapon (previous and next)
cooldown=20
weapon=2
if getfield(subweaponkey1) then subweapon=(subweapon-1)%6 end --previous
if getfield(subweaponkey2) then subweapon=(subweapon+1)%6 end --next
subX=93+25*subweapon subY=48+25*subweapon
stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
stylus.set{x=232, y=34, touch=true} emu.frameadvance()
stylus.set{x=232, y=34, touch=true} emu.frameadvance()
stylus.set{x=subX, y=subY, touch=true} emu.frameadvance()
stylus.set{x=subX, y=subY, touch=true}
end
tmp = getfield(leftstickx)
if tmp and tmp < -deadzone then
joysend.left = true
elseif tmp and tmp > deadzone then
joysend.right = true
end
tmp = getfield(leftsticky)
if tmp and tmp < -deadzone then
joysend.up = true
elseif tmp and tmp > deadzone then
joysend.down = true
end
tmp = getfield(rightstickx)
if tmp and math.abs(tmp)>deadzone and togglestylus==false then
memory.writedword(0x020DEDA6, (tmp * 4))
toggleaim=false
end
tmp = getfield(rightsticky)
if tmp and math.abs(tmp)>deadzone and togglestylus==false then
memory.writedword(0x020DEDAE, (tmp * 6))
toggleaim=false
end
if getfield(boostkey) then
joysend.R = true
end
tmp = getfield(shootkey)
if tmp then
if type(tmp) == "number" then
if math.abs(tmp) > deadzone then
joysend.L = true
end
elseif type(tmp) == "boolean" then
joysend.L = true
end
end
if getfield(jumpkey) then
joysend.B = true
end
if getfield(startkey) then
joysend.start = true
end
end
if cooldown>0 then
cooldown=cooldown-1
else
ball = memory.readbyte(0x020DB098) == 0x02 --Is this a good way of detecting morph ball status??
if ball==false and togglestylus==false and toggleaim==false then
stylus.set{x=128, y=96, touch=true} --Required for aiming
end
end
joypad.set(1,joysend)
emu.frameadvance()
end
What region game are you using? This was only tested on the US version. It has hardcoded memory addresses that it injects values at that probably won't work with other regions.
EDIT: Specifically the first release, not Rev 1. The working ROM has the SHA1 hash 90164D1AC127EE5F9815EA4AE7DE798C7B5FC629
View -> HUD -> Display Input
Also, it looks like input in Lua was broken for the last month in DeSmuME builds, but is now fixed. If you get an error when trying to run one of the scripts then make sure you have an up-to-date build of the emulator.
I'm actually new to adjusting scripts in DesMume and I'm a visual learner, so I was wondering if you could post of video of how to do this? (I'm sure you would get a lot of views on youtube as well considering no one has done this.)
I always wanted to play Metroid prime hunters but trying to use my right hand for my laptop's mouse and my left hand for the controller is a horrible gaming experience.Thank you in advance for your time!
What are you having trouble with?
To run Lua scripts you will need the correct dlls in the folder with your DeSmuME exe. A Lua setup guide copied from here:
Download the Lua DLL that matches your Desmume: - https://sourceforge.net/projects/luabin … s/Dynamic/
lua-5.1.5_Win32_dll14_lib.zip for x86 Desmume
lua-5.1.5_Win64_dll14_lib.zip for x86-64 DesmumeExtract lua5.1.dll from the .zip file to the same folder where your DeSmuME_0.9.11_x86.exe or DeSmuME_0.9.11_x64.exe is
Rename lua5.1.dll to lua51.dll
Copy the script from my post into a text file and rename the extension to ".lua". In DeSmuME once the game is running go to "Tools -> Lua Scripting -> New Lua Script Window...". In the new window that pops up hit browse and select the script you saved and it will start running.
If you want to adjust the controls then use this lua script to identify your controller and its buttons
dpad = {"up", "down", "left", "right"}
sticks = {"x", "y", "z", "r", "u", "v"}
function showControllers()
gui.box(0, 0, 256, 192, "#808080")
gui.box(0, -192, 256, 0, "#808080")
for i=0,15 do
cont = controller.get(i)
if type(next(cont)) ~= "nil" then
gui.text(0, (18*i)-143, i)
xoffs = 14
for ii=0,32 do
col = "#606060"
tmp = tostring(ii)
if cont[tmp] ~= nil then
if cont[tmp] == true then col = "#FF0000" end
gui.text(xoffs, (18*i)-143, tmp, col) --goes off screen if controller has more than ~20 buttons.
xoffs = xoffs + string.len(tmp)*6 + 2
end
end
xoffs = 14
for idx, name in ipairs(dpad) do
col = "#606060"
if cont[name] ~= nil then
if cont[name] == true then col = "#FF0000" end
gui.text(xoffs, (18*i)-143+9, name, col)
xoffs = xoffs + string.len(name)*6 + 4
end
end
for idx, name in ipairs(sticks) do
col = "#606060"
if cont[name] ~= nil then
if math.abs(cont[name]) > 0.25 then col = "#FF0000" end
gui.text(xoffs, (18*i)-143+9, name, col)
xoffs = xoffs + string.len(name)*6 + 2
end
end
end
end
end
gui.register(showControllers)
You should see output similar to this, but different depending on your controller and how many of them you have hooked up.
The white number on the left is the controller id. When you press a button it will turn red to help you identify which button corresponds to which number.
In the main script you can then adjust joyid and the controls with the values you find here.
Updated the script to include a "virtual stylus" for navigating stuff like your ship and the main menu. When holding select(on an xbox style controller) a virtual stylus will be displayed. It can be moved with the right stick and touch the screen with the right bumper. You can still toggle manual touchscreen input if needed with select + left bumper.
I've been sitting on this for a while(years) now and decided to clean it up a bit and post it. This script allows you to play Metroid Prime hunters with a controller with smooth camera movement.
Some small snippets(like subweapon switching) were borrowed from https://forums.desmume.org/viewtopic.php?id=10957 .
This may only work on the Windows version of DeSmuME, I'm not sure.
This requires the US ROM with a SHA1 hash of 90164D1AC127EE5F9815EA4AE7DE798C7B5FC629
Clear any controller input you may have set up in DeSmuME before using.
By default it's set up for an Xbox style controller but the controls are easily customizable.
left stick - move
right stick - aim
left or right trigger - shoot/scan
bumpers - cycle subweapon forward/back
start - start
d-pad up(hold) - scan visor
d-pad down - morphball
d-pad left - presses back arrow on messages
d-pad right - presses forward arrow on messages
B - presses OK on messages
A - jump
X - morphball boost
Y - cycle weapon(beam/missile/subweapon)
select(hold) - virtual stylus
while virtual stylus is active
right stick - move the virtual stylus
right bumper - tap the screen
left bumper - toggle manual touchscreen input
The way aiming works is the center of the screen is continually pressed and the script injects stylus movement into the game's memory. If you need to manually use the touchscreen then use select+left bumper to toggle aiming on or off. That shouldn't be necessary with the existence of the virtual stylus, though.
Use with the U.S. version of the game.
joyid = 0 --change this if your preferred controller isn't being picked up
deadzone = 0.15 --Adjust analog stick deadzones
cooldown=0
weapon=0
subweapon=0
togglestylus=false
toggleaim=false
vcurx=128
vcury=96
key = {}
joy = {}
--Set the controls here
morphkey = "joy.down" --morph ball switch
visorkey = "joy.up" --scan visor switch(hold)
okkey = "joy.2" --ok button in messages
leftkey = "joy.left" --left arrow in messages
rightkey = "joy.right" --right arrow in messages
weaponkey = "joy.4" --weapon cycle
subweaponkey1 = "joy.5" --subweapon cycle back
subweaponkey2 = "joy.6" --subweapon cycle forward
leftstickx = "joy.x" --must be analog stick axis
leftsticky = "joy.y" --must be analog stick axis
rightstickx = "joy.u" --must be analog stick axis
rightsticky = "joy.r" --must be analog stick axis
shootkey = "joy.z" --may be either analog axis or button
boostkey = "joy.3" --morph ball boost
vstyluskey = "joy.7" --virtual stylus(hold)
jumpkey = "joy.1" --jump
startkey = "joy.8" --start
--https://www.lua.org/pil/14.1.html
function getfield (f)
local v = _G -- start with the table of globals
for w in string.gfind(f, "[%w_]+") do
v = v[w]
end
return v
end
while true do
joysend = {}
key=input.get()
joy = controller.get(joyid)
if getfield(vstyluskey) then
toggleaim = true --this exists to just delay the pressing of the screen when you release the virtual stylus key
if vcury>0 then
gui.drawbox(vcurx-1, vcury-1, vcurx+1, vcury+1, "#000000", "#FFFFFF")
else
gui.drawbox(vcurx-1, vcury, vcurx+1, vcury+1, "#FFFFFF") --workaround for vcury=0, as it would draw to the top screen
gui.pixel(vcurx, vcury, "#000000")
end
if getfield(subweaponkey2) then
stylus.set{x=vcurx, y=vcury, touch=true}
end
if getfield(subweaponkey1) and cooldown==0 then
togglestylus = not togglestylus
print('Touchscreen input allowed = ' .. tostring(togglestylus))
cooldown = 20
end
tmp = getfield(rightstickx)
if tmp and math.abs(tmp)>deadzone then
vcurx = vcurx + (tmp * 5)
end
tmp = getfield(rightsticky)
if tmp and math.abs(tmp)>deadzone then
vcury = vcury + (tmp * 5)
end
if vcurx<0 then vcurx=0 end
if vcurx>255 then vcurx=255 end
if vcury<0 then vcury=0 end
if vcury>191 then vcury=191 end
else
if getfield(morphkey) then --Morph
stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
stylus.set{x=231, y=167, touch=true} emu.frameadvance() emu.frameadvance()
end
if getfield(visorkey) then --Visor
if cooldown == 0 then
stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
end
cooldown = 10
stylus.set{x=128, y=173, touch=true}
end
if getfield(okkey) then --OK (in scans and messages)
stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
stylus.set{x=128, y=142, touch=true} emu.frameadvance() emu.frameadvance()
end
if getfield(leftkey) then --Left arrow (in scans and messages)
stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
stylus.set{x=71, y=141, touch=true} emu.frameadvance() emu.frameadvance()
end
if getfield(rightkey) then --Right arrow (in scans and messages)
stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
stylus.set{x=185, y=141, touch=true} emu.frameadvance() emu.frameadvance()
end
if getfield(weaponkey) and cooldown==0 then --Switch weapon (beam->missile->subweapon->beam)
cooldown=20
weapon=(weapon+1)%3
stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
stylus.set{x=85+40*weapon, y=32, touch=true} emu.frameadvance()
stylus.set{x=85+40*weapon, y=32, touch=true}
end
if (getfield(subweaponkey1) or getfield(subweaponkey2)) and cooldown==0 then --Switch subweapon (previous and next)
cooldown=20
weapon=2
if getfield(subweaponkey1) then subweapon=(subweapon-1)%6 end --previous
if getfield(subweaponkey2) then subweapon=(subweapon+1)%6 end --next
subX=93+25*subweapon subY=48+25*subweapon
stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
stylus.set{x=232, y=34, touch=true} emu.frameadvance()
stylus.set{x=232, y=34, touch=true} emu.frameadvance()
stylus.set{x=subX, y=subY, touch=true} emu.frameadvance()
stylus.set{x=subX, y=subY, touch=true}
end
tmp = getfield(leftstickx)
if tmp and tmp < -deadzone then
joysend.left = true
elseif tmp and tmp > deadzone then
joysend.right = true
end
tmp = getfield(leftsticky)
if tmp and tmp < -deadzone then
joysend.up = true
elseif tmp and tmp > deadzone then
joysend.down = true
end
tmp = getfield(rightstickx)
if tmp and math.abs(tmp)>deadzone and togglestylus==false then
memory.writedword(0x020DE526, (tmp * 4))
toggleaim=false
end
tmp = getfield(rightsticky)
if tmp and math.abs(tmp)>deadzone and togglestylus==false then
memory.writedword(0x020DE52E, (tmp * 6))
toggleaim=false
end
if getfield(boostkey) then
joysend.R = true
end
tmp = getfield(shootkey)
if tmp then
if type(tmp) == "number" then
if math.abs(tmp) > deadzone then
joysend.L = true
end
elseif type(tmp) == "boolean" then
joysend.L = true
end
end
if getfield(jumpkey) then
joysend.B = true
end
if getfield(startkey) then
joysend.start = true
end
end
if cooldown>0 then
cooldown=cooldown-1
else
ball = memory.readbyte(0x020DA818) == 0x02 --Is this a good way of detecting morph ball status??
if ball==false and togglestylus==false and toggleaim==false then
stylus.set{x=128, y=96, touch=true} --Required for aiming
end
end
joypad.set(1,joysend)
emu.frameadvance()
end
video recorded while using this script: https://files.catbox.moe/o6r3r5.webm
So, how would I reverse or change the controller assignments?
For example, in some games, when you push the L or R stick right, the camera pans in the opposite direction.
If both the up/down and left/right of the stick are inverted you can try using the dpad to set a negative scale. If you only need one axis inverted then you're on your own. The code's quite ugly but there are comments by the bits of code handling each button/stick. You should be able to slap(or remove) a '-' in there somewhere to invert the axis you need.
edit:
For example, to invert just the left/right of the left stick you'd change "MoveRight(-key.x * movscale);" to "MoveRight(key.x * movscale);". For up/down on the right stick you'd change "pitch = (pitch + (key.r * rotscale)) % 360;" to "pitch = (pitch + (-key.r * rotscale)) % 360;"
Total coding noob here... I'm trying wrap my head around how to use lua code, and I can't wrap my head around how to get the freelook script to work. I get that I actually have to adjust some things in the code myself, but I'm unclear about what values need changing, and no matter what I do the code stops running because register3devent is a nil value..? Even when I think I'm defining it correctly?
Freecam is a feature I'd really like, but I'm too tech-illiterate to wrap my head around how the lua script works, even with the instructions given on the github and in the script itself.. help?
You need a recent-ish build of the emulator.
Hey, what emulator build are you using, and how would I get this to work with a 9.12 emulator with decent speed and graphics?
https://desmume.org/download/
Scroll down to "Official Nightly Builds"
Got a new controller recently and was messing around with this a bit. Updated the first post but here's the changelog.
*Now attempts to scan for a suitable controller instead of just using the first one
*Only updates the matrix on 3D events(still don't think I'm doing it properly, but this fixes most of the issues I had)
*Left and right bumpers now act as multipliers for the dpad scale settings
*Y button resets movement and rotation scale to 1.0
It looks like we need your fork. Which is now AWOL.
Even if it's sloppy, paste your code onto our master and send a PR and I'll make sure it's OK
also I too was missing math.abs.. I have no idea why (don't want to know; I hate lua) but anyway here is a math.abs:math.abs = function(x)
if x == nil then return 0 end
if x<0 then return -x end
return x;
end
My fork was just what the queefersutherland guy PR'd, I don't think he made any changes to it. I didn't want to make a PR myself. The script just assumes you have a controller plugged in that's joystick id 0 and has the 3 axes it checks for, and that causes an error when trying to use them if they don't exist. It really should check for the axes before trying to use them.
EDIT: A version with keyboard controls and mouse aiming is available here
Putting this here to get people to stop replying in the issue page on github.
DeSmuME now has the ability to implement freelook through Lua scripting. Read zeromus' explanation on that here.
I've created this crappy script to look around with a controller. Honestly have no clue what I'm doing, but it somehow works well enough with most games I've tried. It's not a universal solution, though. There's a bunch of games that it doesn't work that great or at all with.
Some webms showing it off:
https://files.catbox.moe/zfni2x.webm
https://files.catbox.moe/0fn3dt.webm
https://files.catbox.moe/s8hev7.webm
Use with something like an xbox 360 controller, or modify the controls to suit your needs.
Left stick moves left/right and forward/back
Right stick rotates
Triggers move up down(according to where you're looking)A button resets position, rotation, and pitch offset
B button resets rotation and pitch offset
X sets pitch offset
Y resets the movement and rotation scaleDpad Up/Down sets the movement scale
Dpad Left/Right sets the rotation scale
Hold the left or right bumper to make smaller or bigger changes to the scalesSetting the pitch offset is important. Without doing so, your view will roll around as you look left and right in most games.
Angle your pitch towards the "horizon" and press X. Then try and rotate the camera 90 degrees to the left or right and adjust up/down a little while pressing X until everything's level.
--Assumes you have an Xbox style controller
joyID = -1; --Joystick ID to use. The script will attempt to find a suitable joystick, but you can manually set this if you wish
yaw = 0.0;
pitch = 0.0;
pitchoffs = 0.0; --used to correct for the initial pitch of the camera
xpos = 0.0; --coordinates in a virtual 3D space
ypos = 0.0; --^^^
zpos = 0.0; --^^^
matAdjustment = {1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1};
movscale = 1.0;
rotscale = 1.0;
cooldown = 0;
function MatrixMult4x4(m1, m2) --sorta copied/inspired from some lua matrix library
local mtx = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
for i=1,4 do
for j=1,4 do
local num = 0
for n=1,4 do
num = num + m1[(i-1)*4+n] * m2[(n-1)*4+j]
mtx[(i-1)*4+j] = num
end
end
end
return mtx;
end
function Rotate(angle,x,y,z)
local s = math.sin(math.rad(angle));
local c = math.cos(math.rad(angle));
return {(1-c)*x*x+c, (1-c)*y*x-s*z, (1-c)*z*x+s*y, 0,
(1-c)*x*y+s*z, (1-c)*y*y+c, (1-c)*z*y-s*x, 0,
(1-c)*x*z-s*y, (1-c)*y*z+s*x, (1-c)*z*z+c, 0,
0, 0, 0, 1};
end
function vecrotx(angle, vx, vy, vz) --used for pitch correction in the functions below
return vx,
vy * math.cos(math.rad(angle)) - vz * math.sin(math.rad(angle)),
vy * math.sin(math.rad(angle)) + vz * math.cos(math.rad(angle));
end
function MoveForward(l) --sorta based off https://www.gamedev.net/forums/topic/415144-get-forward-right-up-vectors-from-pitch-yaw-roll/
local fX = math.sin(math.rad(yaw)) * math.cos(math.rad(pitch+pitchoffs));
local fY = -math.sin(math.rad(pitch+pitchoffs));
local fZ = -math.cos(math.rad(yaw)) * math.cos(math.rad(pitch+pitchoffs));
fX, fY, fZ = vecrotx(pitchoffs, fX, fY, fZ);
local mag = math.sqrt(fX*fX + fY*fY + fZ*fZ);
fX = fX / mag; fY = fY / mag; fZ = fZ / mag;
fX = fX * l; fY = fY * l; fZ = fZ * l;
xpos = xpos + fX; ypos = ypos + fY; zpos = zpos + fZ;
end
function MoveUp(l)
local fX = math.sin(math.rad(yaw)) * math.cos(math.rad(pitch+pitchoffs+90));
local fY = -math.sin(math.rad(pitch+pitchoffs+90));
local fZ = -math.cos(math.rad(yaw)) * math.cos(math.rad(pitch+pitchoffs+90));
fX, fY, fZ = vecrotx(pitchoffs, fX, fY, fZ);
local mag = math.sqrt(fX*fX + fY*fY + fZ*fZ);
fX = fX / mag; fY = fY / mag; fZ = fZ / mag;
fX = fX * l; fY = fY * l; fZ = fZ * l;
xpos = xpos + fX; ypos = ypos + fY; zpos = zpos + fZ;
end
function MoveRight(l)
local fX = math.sin(math.rad(yaw+90));
local fY = 0;
local fZ = -math.cos(math.rad(yaw+90));
fX, fY, fZ = vecrotx(pitchoffs, fX, fY, fZ);
local mag = math.sqrt(fX*fX + fY*fY + fZ*fZ);
fX = fX / mag; fY = fY / mag; fZ = fZ / mag;
fX = fX * l; fY = fY * l; fZ = fZ * l;
xpos = xpos + fX; ypos = ypos + fY; zpos = zpos + fZ;
end
function on3d()
emu.set3dtransform(2,matAdjustment);
end
emu.register3devent(on3d);
emu.set3dtransform(1,nil);
if joyID == -1 then
for i = 0, 15 do
c = controller.get(i);
if c.x and c.y and c.z and c.u and c.r then --simple check that only looks for all the needed axes
joyID = i;
print("Found suitable joystick, id " .. i);
break;
end
end
else
print("Using manually set joystick, id " .. joyID);
end
if joyID == -1 then
print("No suitable joystick found!");
while true do
emu.frameadvance();
end
end
while true do
local key = controller.get(joyID)
scalemult = 1;
if key["5"] then --Left bumper
scalemult = 0.1;
elseif key["6"] then --Right bumper
scalemult = 10;
end
if key.up and cooldown==0 then --Dpad
cooldown = 10;
movscale = movscale + (0.1 * scalemult);
print("Movement scale: " .. movscale);
end
if key.down and cooldown==0 then --Dpad
cooldown = 10;
movscale = movscale - (0.1 * scalemult);
print("Movement scale: " .. movscale);
end
if key.right and cooldown==0 then --Dpad
cooldown = 10;
rotscale = rotscale + (0.1 * scalemult);
print("Rotation scale: " .. rotscale);
end
if key.left and cooldown==0 then --Dpad
cooldown = 10;
rotscale = rotscale - (0.1 * scalemult);
print("Rotation scale: " .. rotscale);
end
if(math.abs(key.x)>0.25) then --Left Stick
MoveRight(-key.x * movscale);
end
if(math.abs(key.y)>0.25) then --Left Stick
MoveForward(key.y * movscale);
end
if(math.abs(key.z)>0.25) then --Triggers
MoveUp(-key.z * movscale);
end
if(math.abs(key.u)>0.25) then --Right Stick
yaw = (yaw + (key.u * rotscale)) % 360;
end
if(math.abs(key.r)>0.25) then --Right Stick
pitch = (pitch + (key.r * rotscale)) % 360;
end
if key["1"] and cooldown==0 then --A button
cooldown = 20;
yaw = 0.0;
pitch = 0.0;
pitchoffs = 0.0;
xpos = 0.0;
ypos = 0.0;
zpos = 0.0;
movscale = 1.0;
rotscale = 1.0;
print("Reset everything");
end
if key["2"] and cooldown==0 then --B button
cooldown = 20;
yaw = 0.0;
pitch = 0.0;
pitchoffs = 0.0;
print("Reset rotations");
end
if key["3"] then --X button
pitchoffs = -pitch;
end
if key["4"] and cooldown==0 then --Y button
cooldown = 20;
movscale = 1.0;
rotscale = 1.0;
print("Movement/rotation scales reset");
end
local tmp = Rotate(-yaw, 0.0, math.cos(math.rad(pitchoffs)), math.sin(math.rad(pitchoffs)));
tmp = MatrixMult4x4(tmp, Rotate(-pitch, 1.0, 0.0, 0.0));
local translation = {1,0,0,0,0,1,0,0,0,0,1,0,xpos,ypos,zpos,1};
matAdjustment = MatrixMult4x4(translation, tmp);
if cooldown>0 then cooldown=cooldown-1 end
emu.frameadvance();
end
I've come up with a simpler code. I hadn't actually looked into the game's disassembly when I made that code earlier. I found the values being set in no$gba's i/o map viewer and searched for them in memory, then made the code to zero them. Turns out the game has support to just set those values to zero if a certain read value is zero.
Picross 3D (USA):
0208B5B0 E3A00000
Picross 3D (Europe):
0208B4DC E3A00000
Rittai Picross (Japan):
020A3BBC E3A00000
The game alters part of the texture matrix on alternating frames. You can disable this with this AR code:
-- see below post --
SoftRasterizer easily makes this game playable.
Not really(well, maybe if you have the latest Intel® processor). Like I've already mentioned, for me the software renderer gets the same speed as OpenGL without scaling(~30fps) once I move around the level a bit. The only place where it's running at a playable speed is the very start of the level.
Guess I'll just dig my physical copy and play on the real thing.
This game seems to be doing something with its graphics to cause some severe slowdown. In either the software renderer or the OpenGL renderer, it runs at half speed for me when I start moving around. With the OpenGL renderer and texture scaling set to 2x, the FPS drops down to below 5(see https://i.imgur.com/01TeL0A.png). Seems odd for such a simple looking game. No other games drop to below 55FPS for me with the same settings.